Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa22647acd | ||
|
|
dd5d82fe7a | ||
|
|
98b179aeb5 | ||
|
|
e1f1c005a0 | ||
|
|
6e226c5a4f | ||
|
|
7440fa5a37 | ||
|
|
4fe204605a | ||
|
|
4446b42b82 | ||
|
|
4b6cd17d0a | ||
|
|
1a6e74271c | ||
|
|
6ba3719031 | ||
|
|
dd95e3df7e | ||
|
|
69fd7853c8 | ||
|
|
c01c478ffe | ||
|
|
f8be1da83a | ||
|
|
3a7625486e | ||
|
|
fdc3b6c573 | ||
|
|
76939ed51f | ||
|
|
b9cf761f4a | ||
|
|
4c515ba541 | ||
|
|
d7c3595bf1 | ||
|
|
1fbd6a0824 | ||
|
|
ccb59c7f02 | ||
|
|
04bef3e82a | ||
|
|
17105b98ed | ||
|
|
4bff1515a9 | ||
|
|
0a75893346 | ||
|
|
2ed92467f9 | ||
|
|
634ac122d9 | ||
|
|
44640b7e53 | ||
|
|
47e7b22a7e | ||
|
|
918928d4bb | ||
|
|
69fc172779 | ||
|
|
d84dabbe4d | ||
|
|
23114210c4 | ||
|
|
ea80e5a223 | ||
|
|
6087f31d41 | ||
|
|
30ee292a32 | ||
|
|
705a9319f5 | ||
|
|
c789d9d87c | ||
|
|
a7681b5505 | ||
|
|
9e74d8af0b | ||
|
|
b52061f849 | ||
|
|
01b875c283 | ||
|
|
4cc3b78321 | ||
|
|
6205db87e6 | ||
|
|
518633b153 | ||
|
|
988ee7b7e7 | ||
|
|
cdadde60ce | ||
|
|
4bb01d86d9 | ||
|
|
4cac43520f | ||
|
|
d6dddd16f1 | ||
|
|
c0da054635 | ||
|
|
2b4d94ca55 | ||
|
|
e8e564738a | ||
|
|
d48fbd8b62 | ||
|
|
c1f80f209e | ||
|
|
ed6b32c827 | ||
|
|
fc436fd352 | ||
|
|
ee6fdb1ca1 | ||
|
|
988db30355 | ||
|
|
ea98ee5e99 | ||
|
|
b8d1d43822 | ||
|
|
0d017c6d14 | ||
|
|
2825e9a003 | ||
|
|
6e9ddfcbf2 | ||
|
|
378689be39 | ||
|
|
31858fad12 | ||
|
|
60351d629d | ||
|
|
715a97159a | ||
|
|
b48ce28b35 | ||
|
|
7ab0448cd3 | ||
|
|
5f6642fa63 | ||
|
|
5a0d1ed408 | ||
|
|
131e8fb6be | ||
|
|
1c7fb8ef93 | ||
|
|
8c0ec3957f | ||
|
|
72063a15d9 | ||
|
|
0d1b15aafc | ||
|
|
ca10369bdc | ||
|
|
42af75d8d2 | ||
|
|
a02871dd28 | ||
|
|
e65a8bc648 | ||
|
|
b373b6a34f | ||
|
|
6d6a0255e2 | ||
|
|
003d6a3d5f | ||
|
|
77a2c60fe5 | ||
|
|
ac3bd699ee | ||
|
|
596498c81e | ||
|
|
c95f764c77 | ||
|
|
5c5be05843 | ||
|
|
3fb26ec49e |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -6,7 +6,7 @@ labels: bug
|
|||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
<!--Please be aware that GNOME Code of Conduct applies to Alpaca, https://conduct.gnome.org/-->
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -6,7 +6,7 @@ labels: enhancement
|
|||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
<!--Please be aware that GNOME Code of Conduct applies to Alpaca, https://conduct.gnome.org/-->
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Is your feature request related to a problem? Please describe.**
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
|||||||
18
.github/workflows/flatpak-builder.yml
vendored
Normal file
18
.github/workflows/flatpak-builder.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# .github/workflows/flatpak-build.yml
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
name: Flatpak Build
|
||||||
|
jobs:
|
||||||
|
flatpak:
|
||||||
|
name: "Flatpak"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: bilelmoussaoui/flatpak-github-actions:gnome-46
|
||||||
|
options: --privileged
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||||
|
with:
|
||||||
|
bundle: Alpaca.flatpak
|
||||||
|
manifest-path: com.jeffser.Alpaca.json
|
||||||
|
cache-key: flatpak-builder-${{ github.sha }}
|
||||||
24
.github/workflows/pylint.yml
vendored
Normal file
24
.github/workflows/pylint.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Pylint
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.11"]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v3
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install pylint
|
||||||
|
- name: Analysing the code with pylint
|
||||||
|
run: |
|
||||||
|
pylint --rcfile=.pylintrc $(git ls-files '*.py' | grep -v 'src/available_models_descriptions.py')
|
||||||
14
.pylintrc
Normal file
14
.pylintrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[MASTER]
|
||||||
|
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=undefined-variable, line-too-long, missing-function-docstring, consider-using-f-string, import-error
|
||||||
|
|
||||||
|
[FORMAT]
|
||||||
|
max-line-length=200
|
||||||
|
|
||||||
|
# Reasons for removing some checks:
|
||||||
|
# undefined-variable: _() is used by the translator on build time but it is not defined on the scripts
|
||||||
|
# line-too-long: I... I'm too lazy to make the lines shorter, maybe later
|
||||||
|
# missing-function-docstring I'm not adding a docstring to all the functions, most are self explanatory
|
||||||
|
# consider-using-f-string I can't use f-string because of the translator
|
||||||
|
# import-error The linter doesn't have access to all the libraries that the project itself does
|
||||||
34
Alpaca.doap
Normal file
34
Alpaca.doap
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
|
||||||
|
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||||
|
xmlns:gnome="http://api.gnome.org/doap-extensions#"
|
||||||
|
xmlns="http://usefulinc.com/ns/doap#">
|
||||||
|
|
||||||
|
<name xml:lang="en">Alpaca</name>
|
||||||
|
<shortdesc xml:lang="en">An Ollama client made with GTK4 and Adwaita</shortdesc>
|
||||||
|
<homepage rdf:resource="https://jeffser.com/alpaca" />
|
||||||
|
<bug-database rdf:resource="https://github.com/Jeffser/Alpaca/issues"/>
|
||||||
|
<programming-language>Python</programming-language>
|
||||||
|
|
||||||
|
<platform>GTK 4</platform>
|
||||||
|
<platform>Libadwaita</platform>
|
||||||
|
|
||||||
|
<maintainer>
|
||||||
|
<foaf:Person>
|
||||||
|
<foaf:name>Jeffry Samuel</foaf:name>
|
||||||
|
<foaf:mbox rdf:resource="mailto:jeffrysamuer@gmail.com"/>
|
||||||
|
<foaf:account>
|
||||||
|
<foaf:OnlineAccount>
|
||||||
|
<foaf:accountServiceHomepage rdf:resource="https://github.com"/>
|
||||||
|
<foaf:accountName>jeffser</foaf:accountName>
|
||||||
|
</foaf:OnlineAccount>
|
||||||
|
</foaf:account>
|
||||||
|
<foaf:account>
|
||||||
|
<foaf:OnlineAccount>
|
||||||
|
<foaf:accountServiceHomepage rdf:resource="https://gitlab.gnome.org"/>
|
||||||
|
<foaf:accountName>jeffser</foaf:accountName>
|
||||||
|
</foaf:OnlineAccount>
|
||||||
|
</foaf:account>
|
||||||
|
</foaf:Person>
|
||||||
|
</maintainer>
|
||||||
|
</Project>
|
||||||
54
README.md
54
README.md
@@ -11,7 +11,11 @@ Alpaca is an [Ollama](https://github.com/ollama/ollama) client where you can man
|
|||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> 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 AI models.
|
> 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 AI models.
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Please be aware that [GNOME Code of Conduct](https://conduct.gnome.org) applies to Alpaca before interacting with this repository.
|
||||||
|
|
||||||
## Features!
|
## Features!
|
||||||
|
|
||||||
- Talk to multiple models in the same conversation
|
- Talk to multiple models in the same conversation
|
||||||
- Pull and delete models from the app
|
- Pull and delete models from the app
|
||||||
- Image recognition
|
- Image recognition
|
||||||
@@ -21,47 +25,37 @@ Alpaca is an [Ollama](https://github.com/ollama/ollama) client where you can man
|
|||||||
- Notifications
|
- Notifications
|
||||||
- Import / Export chats
|
- Import / Export chats
|
||||||
- Delete / Edit messages
|
- Delete / Edit messages
|
||||||
|
- Regenerate messages
|
||||||
- YouTube recognition (Ask questions about a YouTube video using the transcript)
|
- YouTube recognition (Ask questions about a YouTube video using the transcript)
|
||||||
- Website recognition (Ask questions about a certain website by parsing the url)
|
- Website recognition (Ask questions about a certain website by parsing the url)
|
||||||
|
|
||||||
## Screenies
|
## Screenies
|
||||||
Chatting with a model | Image recognition | Code highlighting
|
|
||||||
:--------------------:|:-----------------:|:----------------------:
|
|
||||||
 |  | 
|
|
||||||
|
|
||||||
## Preview
|
Normal conversation | Image recognition | Code highlighting | YouTube transcription | Model management
|
||||||
1. Clone repo using Gnome Builder
|
:------------------:|:-----------------:|:-----------------:|:---------------------:|:----------------:
|
||||||
2. Press the `run` button
|
 |  |  |  | 
|
||||||
|
|
||||||
## Instalation
|
## Translators
|
||||||
1. Go to the `releases` page
|
|
||||||
2. Download the latest flatpak package
|
|
||||||
3. Open it
|
|
||||||
|
|
||||||
## Ollama session tips
|
Language | Contributors
|
||||||
|
:----------------------|:-----------
|
||||||
### Change the port of the integrated Ollama instance
|
🇷🇺 Russian | [Alex K](https://github.com/alexkdeveloper)
|
||||||
Go to `~/.var/app/com.jeffser.Alpaca/config/server.json` and change the `"local_port"` value, by default it is `11435`.
|
🇪🇸 Spanish | [Jeffry Samuel](https://github.com/jeffser)
|
||||||
|
🇫🇷 French | [Louis Chauvet-Villaret](https://github.com/loulou64490) , [Théo FORTIN](https://github.com/topiga)
|
||||||
### Backup all the chats
|
🇧🇷 Brazilian Portuguese | [Daimar Stein](https://github.com/not-a-dev-stein)
|
||||||
The chat data is located in `~/.var/app/com.jeffser.Alpaca/data/chats` you can copy that directory wherever you want to.
|
🇳🇴 Norwegian | [CounterFlow64](https://github.com/CounterFlow64)
|
||||||
|
🇮🇳 Bengali | [Aritra Saha](https://github.com/olumolu)
|
||||||
### Force showing the welcome dialog
|
🇨🇳 Simplified Chinese | [Yuehao Sui](https://github.com/8ar10der) , [Aleksana](https://github.com/Aleksanaa)
|
||||||
To do that you just need to delete the file `~/.var/app/com.jeffser.Alpaca/config/server.json`, this won't affect your saved chats or models.
|
|
||||||
|
|
||||||
### Add/Change environment variables for Ollama
|
|
||||||
You can change anything except `$HOME` and `$OLLAMA_HOST`, to do this go to `~/.var/app/com.jeffser.Alpaca/config/server.json` and change `ollama_overrides` accordingly, some overrides are available to change on the GUI.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
- [not-a-dev-stein](https://github.com/not-a-dev-stein) for their help with requesting a new icon, bug reports and the translation to Brazilian Portuguese
|
|
||||||
|
- [not-a-dev-stein](https://github.com/not-a-dev-stein) for their help with requesting a new icon and bug reports
|
||||||
- [TylerLaBree](https://github.com/TylerLaBree) for their requests and ideas
|
- [TylerLaBree](https://github.com/TylerLaBree) for their requests and ideas
|
||||||
- [Alexkdeveloper](https://github.com/alexkdeveloper) for their help translating the app to Russian
|
|
||||||
- [Imbev](https://github.com/imbev) for their reports and suggestions
|
- [Imbev](https://github.com/imbev) for their reports and suggestions
|
||||||
- [Nokse](https://github.com/Nokse22) for their contributions to the UI and table rendering
|
- [Nokse](https://github.com/Nokse22) for their contributions to the UI and table rendering
|
||||||
- [Louis Chauvet-Villaret](https://github.com/loulou64490) for their suggestions and help translating the app to French
|
- [Louis Chauvet-Villaret](https://github.com/loulou64490) for their suggestions
|
||||||
- [CounterFlow64](https://github.com/CounterFlow64) for their help translating the app to Norwegian
|
- [Aleksana](https://github.com/Aleksanaa) for her help with better handling of directories
|
||||||
|
- Sponsors for giving me enough money to be able to take a ride to my campus every time I need to <3
|
||||||
## About forks
|
- Everyone that has shared kind words of encouragement!
|
||||||
If you want to fork this... I mean, I think it would be better if you start from scratch, my code isn't well documented at all, but if you really want to, please give me some credit, that's all I ask for... And maybe a donation (joke)
|
|
||||||
|
|||||||
@@ -122,16 +122,16 @@
|
|||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"url": "https://github.com/ollama/ollama/releases/download/v0.3.0/ollama-linux-amd64",
|
"url": "https://github.com/ollama/ollama/releases/download/v0.3.3/ollama-linux-amd64",
|
||||||
"sha256": "b8817c34882c7ac138565836ac1995a2c61261a79315a13a0aebbfe5435da855",
|
"sha256": "2b2a4ee4c86fa5b09503e95616bd1b3ee95238b1b3bf12488b9c27c66b84061a",
|
||||||
"only-arches": [
|
"only-arches": [
|
||||||
"x86_64"
|
"x86_64"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"url": "https://github.com/ollama/ollama/releases/download/v0.3.0/ollama-linux-arm64",
|
"url": "https://github.com/ollama/ollama/releases/download/v0.3.3/ollama-linux-arm64",
|
||||||
"sha256": "64be908749212052146f1008dd3867359c776ac1766e8d86291886f53d294d4d",
|
"sha256": "28fddbea0c161bc539fd08a3dc78d51413cfe8da97386cb39420f4f30667e22c",
|
||||||
"only-arches": [
|
"only-arches": [
|
||||||
"aarch64"
|
"aarch64"
|
||||||
]
|
]
|
||||||
@@ -145,7 +145,7 @@
|
|||||||
"sources" : [
|
"sources" : [
|
||||||
{
|
{
|
||||||
"type" : "git",
|
"type" : "git",
|
||||||
"url" : "file:///home/tentri/Documents/Alpaca",
|
"url": "https://github.com/Jeffser/Alpaca.git",
|
||||||
"branch" : "main"
|
"branch" : "main"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -80,6 +80,49 @@
|
|||||||
<url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
|
<url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
|
||||||
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
|
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="1.0.6" date="2024-08-04">
|
||||||
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.6</url>
|
||||||
|
<description>
|
||||||
|
<p>New</p>
|
||||||
|
<ul>
|
||||||
|
<li>Changed shortcuts to standards</li>
|
||||||
|
<li>Moved 'Manage Models' button to primary menu</li>
|
||||||
|
<li>Stable support for GGUF model files</li>
|
||||||
|
<li>General optimizations</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fixes</p>
|
||||||
|
<ul>
|
||||||
|
<li>Better handling of enter key (important for Japanese input)</li>
|
||||||
|
<li>Removed sponsor dialog</li>
|
||||||
|
<li>Added sponsor link in about dialog</li>
|
||||||
|
<li>Changed window and elements dimensions</li>
|
||||||
|
<li>Selected model changes when entering model manager</li>
|
||||||
|
<li>Better image tooltips</li>
|
||||||
|
<li>GGUF Support</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="1.0.5" date="2024-08-02">
|
||||||
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.5</url>
|
||||||
|
<description>
|
||||||
|
<p>New</p>
|
||||||
|
<ul>
|
||||||
|
<li>Regenerate any response, even if they are incomplete</li>
|
||||||
|
<li>Support for pulling models by name:tag</li>
|
||||||
|
<li>Stable support for GGUF model files</li>
|
||||||
|
<li>Restored sidebar toggle button</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fixes</p>
|
||||||
|
<ul>
|
||||||
|
<li>Reverted back to standard styles</li>
|
||||||
|
<li>Fixed generated titles having "'S" for some reason</li>
|
||||||
|
<li>Changed min width for model dropdown</li>
|
||||||
|
<li>Changed message entry shadow</li>
|
||||||
|
<li>The last model used is now restored when the user changes chat</li>
|
||||||
|
<li>Better check for message finishing</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="1.0.4" date="2024-08-01">
|
<release version="1.0.4" date="2024-08-01">
|
||||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.4</url>
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.4</url>
|
||||||
<description>
|
<description>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('Alpaca', 'c',
|
project('Alpaca', 'c',
|
||||||
version: '1.0.4',
|
version: '1.0.6',
|
||||||
meson_version: '>= 0.62.0',
|
meson_version: '>= 0.62.0',
|
||||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||||
)
|
)
|
||||||
|
|||||||
948
po/alpaca.pot
948
po/alpaca.pot
File diff suppressed because it is too large
Load Diff
975
po/nb_NO.po
975
po/nb_NO.po
File diff suppressed because it is too large
Load Diff
960
po/pt_BR.po
960
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
1027
po/zh_CN.po
1027
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@
|
|||||||
<file alias="icons/scalable/status/edit-find-symbolic.svg">icons/edit-find-symbolic.svg</file>
|
<file alias="icons/scalable/status/edit-find-symbolic.svg">icons/edit-find-symbolic.svg</file>
|
||||||
<file alias="icons/scalable/status/edit-symbolic.svg">icons/edit-symbolic.svg</file>
|
<file alias="icons/scalable/status/edit-symbolic.svg">icons/edit-symbolic.svg</file>
|
||||||
<file alias="icons/scalable/status/image-missing-symbolic.svg">icons/image-missing-symbolic.svg</file>
|
<file alias="icons/scalable/status/image-missing-symbolic.svg">icons/image-missing-symbolic.svg</file>
|
||||||
|
<file alias="icons/scalable/status/update-symbolic.svg">icons/update-symbolic.svg</file>
|
||||||
<file preprocess="xml-stripblanks">window.ui</file>
|
<file preprocess="xml-stripblanks">window.ui</file>
|
||||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
# connection_handler.py
|
# connection_handler.py
|
||||||
import json, requests
|
"""
|
||||||
|
Handles requests to remote and integrated instances of Ollama
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
#OK=200 response.status_code
|
#OK=200 response.status_code
|
||||||
url = None
|
URL = None
|
||||||
bearer_token = None
|
BEARER_TOKEN = None
|
||||||
|
|
||||||
def get_headers(include_json:bool) -> dict:
|
def get_headers(include_json:bool) -> dict:
|
||||||
headers = {}
|
headers = {}
|
||||||
if include_json:
|
if include_json:
|
||||||
headers["Content-Type"] = "application/json"
|
headers["Content-Type"] = "application/json"
|
||||||
if bearer_token:
|
if BEARER_TOKEN:
|
||||||
headers["Authorization"] = "Bearer {}".format(bearer_token)
|
headers["Authorization"] = "Bearer {}".format(BEARER_TOKEN)
|
||||||
return headers if len(headers.keys()) > 0 else None
|
return headers if len(headers.keys()) > 0 else None
|
||||||
|
|
||||||
def simple_get(connection_url:str) -> dict:
|
def simple_get(connection_url:str) -> dict:
|
||||||
|
|||||||
101
src/dialogs.py
101
src/dialogs.py
@@ -1,11 +1,15 @@
|
|||||||
# dialogs.py
|
# dialogs.py
|
||||||
|
"""
|
||||||
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
Handles UI dialogs
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from pytube import YouTube
|
from pytube import YouTube
|
||||||
from html2text import html2text
|
from html2text import html2text
|
||||||
|
from gi.repository import Adw, Gtk
|
||||||
from . import connection_handler
|
from . import connection_handler
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
# CLEAR CHAT | WORKS
|
# CLEAR CHAT | WORKS
|
||||||
|
|
||||||
def clear_chat_response(self, dialog, task):
|
def clear_chat_response(self, dialog, task):
|
||||||
@@ -54,9 +58,11 @@ def delete_chat(self, chat_name):
|
|||||||
# RENAME CHAT | WORKS
|
# RENAME CHAT | WORKS
|
||||||
|
|
||||||
def rename_chat_response(self, dialog, task, old_chat_name, entry, label_element):
|
def rename_chat_response(self, dialog, task, old_chat_name, entry, label_element):
|
||||||
if not entry: return
|
if not entry:
|
||||||
|
return
|
||||||
new_chat_name = entry.get_text()
|
new_chat_name = entry.get_text()
|
||||||
if old_chat_name == new_chat_name: return
|
if old_chat_name == new_chat_name:
|
||||||
|
return
|
||||||
if new_chat_name and (task is None or dialog.choose_finish(task) == "rename"):
|
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)
|
self.rename_chat(old_chat_name, new_chat_name, label_element)
|
||||||
|
|
||||||
@@ -82,7 +88,8 @@ def rename_chat(self, chat_name, label_element):
|
|||||||
|
|
||||||
def new_chat_response(self, dialog, task, entry):
|
def new_chat_response(self, dialog, task, entry):
|
||||||
chat_name = _("New Chat")
|
chat_name = _("New Chat")
|
||||||
if entry is not None and entry.get_text() != "": chat_name = entry.get_text()
|
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"):
|
if chat_name and (task is None or dialog.choose_finish(task) == "create"):
|
||||||
self.new_chat(chat_name)
|
self.new_chat(chat_name)
|
||||||
|
|
||||||
@@ -224,7 +231,7 @@ def create_model_from_existing_response(self, dialog, task, dropdown):
|
|||||||
def create_model_from_existing(self):
|
def create_model_from_existing(self):
|
||||||
string_list = Gtk.StringList()
|
string_list = Gtk.StringList()
|
||||||
for model in self.local_models:
|
for model in self.local_models:
|
||||||
string_list.append(model)
|
string_list.append(self.convert_model_name(model, 0))
|
||||||
|
|
||||||
dropdown = Gtk.DropDown()
|
dropdown = Gtk.DropDown()
|
||||||
dropdown.set_model(string_list)
|
dropdown.set_model(string_list)
|
||||||
@@ -243,20 +250,41 @@ def create_model_from_existing(self):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def create_model_from_file_response(self, file_dialog, result):
|
def create_model_from_file_response(self, file_dialog, result):
|
||||||
try: file = file_dialog.open_finish(result)
|
try:
|
||||||
except:
|
file = file_dialog.open_finish(result)
|
||||||
self.logger.error(e)
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
self.create_model(file.get_path(), True)
|
self.create_model(file.get_path(), True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
self.show_toast(_("An error occurred while creating the model"), self.main_overlay)
|
self.show_toast(_("An error occurred while creating the model"), self.main_overlay)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
def create_model_from_file(self):
|
def create_model_from_file(self):
|
||||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_gguf)
|
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_gguf)
|
||||||
file_dialog.open(self, None, lambda file_dialog, result: create_model_from_file_response(self, file_dialog, result))
|
file_dialog.open(self, None, lambda file_dialog, result: create_model_from_file_response(self, file_dialog, result))
|
||||||
|
|
||||||
|
def create_model_from_name_response(self, dialog, task, entry):
|
||||||
|
model = entry.get_text().lower().strip()
|
||||||
|
if dialog.choose_finish(task) == 'accept' and model:
|
||||||
|
self.pull_model(model)
|
||||||
|
|
||||||
|
def create_model_from_name(self):
|
||||||
|
entry = Gtk.Entry()
|
||||||
|
entry.get_delegate().connect("insert-text", self.check_alphanumeric)
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Pull Model"),
|
||||||
|
body=_("Input the name of the model in this format\nname:tag"),
|
||||||
|
extra_child=entry
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("accept", _("Accept"))
|
||||||
|
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, entry=entry: create_model_from_name_response(self, dialog, task, entry)
|
||||||
|
)
|
||||||
# FILE CHOOSER | WORKS
|
# FILE CHOOSER | WORKS
|
||||||
|
|
||||||
def attach_file_response(self, file_dialog, result):
|
def attach_file_response(self, file_dialog, result):
|
||||||
@@ -265,24 +293,24 @@ def attach_file_response(self, file_dialog, result):
|
|||||||
"image": ["png", "jpeg", "jpg", "webp", "gif"],
|
"image": ["png", "jpeg", "jpg", "webp", "gif"],
|
||||||
"pdf": ["pdf"]
|
"pdf": ["pdf"]
|
||||||
}
|
}
|
||||||
try: file = file_dialog.open_finish(result)
|
try:
|
||||||
except:
|
file = file_dialog.open_finish(result)
|
||||||
self.logger.error(e)
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
return
|
return
|
||||||
extension = file.get_path().split(".")[-1]
|
extension = file.get_path().split(".")[-1]
|
||||||
file_type = next(key for key, value in file_types.items() if extension in value)
|
file_type = next(key for key, value in file_types.items() if extension in value)
|
||||||
if not file_type: return
|
if not file_type:
|
||||||
|
return
|
||||||
if file_type == 'image' and not self.verify_if_image_can_be_used():
|
if file_type == 'image' and not self.verify_if_image_can_be_used():
|
||||||
self.show_toast(_("Image recognition is only available on specific models"), self.main_overlay)
|
self.show_toast(_("Image recognition is only available on specific models"), self.main_overlay)
|
||||||
return
|
return
|
||||||
self.attach_file(file.get_path(), file_type)
|
self.attach_file(file.get_path(), file_type)
|
||||||
|
|
||||||
|
def attach_file(self, file_filter):
|
||||||
def attach_file(self, filter):
|
file_dialog = Gtk.FileDialog(default_filter=file_filter)
|
||||||
file_dialog = Gtk.FileDialog(default_filter=filter)
|
|
||||||
file_dialog.open(self, None, lambda file_dialog, result: attach_file_response(self, file_dialog, result))
|
file_dialog.open(self, None, lambda file_dialog, result: attach_file_response(self, file_dialog, result))
|
||||||
|
|
||||||
|
|
||||||
# YouTube caption | WORKS
|
# YouTube caption | WORKS
|
||||||
|
|
||||||
def youtube_caption_response(self, dialog, task, video_url, caption_drop_down):
|
def youtube_caption_response(self, dialog, task, video_url, caption_drop_down):
|
||||||
@@ -300,7 +328,7 @@ def youtube_caption_response(self, dialog, task, video_url, caption_drop_down):
|
|||||||
if not os.path.exists(os.path.join(self.cache_dir, 'tmp/youtube')):
|
if not os.path.exists(os.path.join(self.cache_dir, 'tmp/youtube')):
|
||||||
os.makedirs(os.path.join(self.cache_dir, 'tmp/youtube'))
|
os.makedirs(os.path.join(self.cache_dir, 'tmp/youtube'))
|
||||||
file_path = os.path.join(os.path.join(self.cache_dir, 'tmp/youtube'), f'{yt.title} ({selected_caption.split(" | ")[0]})')
|
file_path = os.path.join(os.path.join(self.cache_dir, 'tmp/youtube'), f'{yt.title} ({selected_caption.split(" | ")[0]})')
|
||||||
with open(file_path, 'w+') as f:
|
with open(file_path, 'w+', encoding="utf-8") as f:
|
||||||
f.write(text)
|
f.write(text)
|
||||||
self.attach_file(file_path, 'youtube')
|
self.attach_file(file_path, 'youtube')
|
||||||
|
|
||||||
@@ -312,7 +340,8 @@ def youtube_caption(self, video_url):
|
|||||||
self.show_toast(_("This video does not have any transcriptions"), self.main_overlay)
|
self.show_toast(_("This video does not have any transcriptions"), self.main_overlay)
|
||||||
return
|
return
|
||||||
caption_list = Gtk.StringList()
|
caption_list = Gtk.StringList()
|
||||||
for caption in captions: caption_list.append("{} | {}".format(caption.name, caption.code))
|
for caption in captions:
|
||||||
|
caption_list.append("{} | {}".format(caption.name, caption.code))
|
||||||
caption_drop_down = Gtk.DropDown(
|
caption_drop_down = Gtk.DropDown(
|
||||||
enable_search=True,
|
enable_search=True,
|
||||||
model=caption_list
|
model=caption_list
|
||||||
@@ -348,7 +377,7 @@ def attach_website_response(self, dialog, task, url):
|
|||||||
os.makedirs('/tmp/alpaca/websites/')
|
os.makedirs('/tmp/alpaca/websites/')
|
||||||
md_name = self.generate_numbered_name('website.md', os.listdir('/tmp/alpaca/websites'))
|
md_name = self.generate_numbered_name('website.md', os.listdir('/tmp/alpaca/websites'))
|
||||||
file_path = os.path.join('/tmp/alpaca/websites/', md_name)
|
file_path = os.path.join('/tmp/alpaca/websites/', md_name)
|
||||||
with open(file_path, 'w+') as f:
|
with open(file_path, 'w+', encoding="utf-8") as f:
|
||||||
f.write('{}\n\n{}'.format(url, md))
|
f.write('{}\n\n{}'.format(url, md))
|
||||||
self.attach_file(file_path, 'website')
|
self.attach_file(file_path, 'website')
|
||||||
else:
|
else:
|
||||||
@@ -369,31 +398,3 @@ def attach_website(self, url):
|
|||||||
cancellable = None,
|
cancellable = None,
|
||||||
callback = lambda dialog, task, url=url: attach_website_response(self, dialog, task, url)
|
callback = lambda dialog, task, url=url: attach_website_response(self, dialog, task, url)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Begging for money :3
|
|
||||||
|
|
||||||
def support_response(self, dialog, task):
|
|
||||||
res = dialog.choose_finish(task)
|
|
||||||
if res == 'later': return
|
|
||||||
elif res == 'support':
|
|
||||||
self.show_toast(_("Thank you!"), self.main_overlay)
|
|
||||||
os.system('xdg-open https://github.com/sponsors/Jeffser')
|
|
||||||
self.show_support = False
|
|
||||||
self.save_server_config()
|
|
||||||
|
|
||||||
def support(self):
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Support"),
|
|
||||||
body=_("Are you enjoying Alpaca? Consider sponsoring the project!"),
|
|
||||||
close_response="nope"
|
|
||||||
)
|
|
||||||
dialog.add_response("nope", _("Don't show again"))
|
|
||||||
dialog.set_response_appearance("nope", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.add_response("later", _("Later"))
|
|
||||||
dialog.add_response("support", _("Support"))
|
|
||||||
dialog.set_response_appearance("support", Adw.ResponseAppearance.SUGGESTED)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task: support_response(self, dialog, task)
|
|
||||||
)
|
|
||||||
|
|||||||
2
src/icons/update-symbolic.svg
Normal file
2
src/icons/update-symbolic.svg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 7.957031 2 c -0.082031 0 -0.164062 0.003906 -0.246093 0.007812 c -0.1875 0.011719 -0.375 0.03125 -0.5625 0.0625 c -1.582032 0.226563 -3.007813 1.070313 -3.96875 2.34375 c -0.804688 1.074219 -1.183594 2.332032 -1.179688 3.585938 h 2.003906 c 0 -0.832031 0.253906 -1.671875 0.796875 -2.398438 c 1.335938 -1.777343 3.820313 -2.113281 5.597657 -0.78125 c 0.429687 0.320313 0.769531 0.734376 1.03125 1.1875 h -1.4375 c -0.550782 0 -1 0.449219 -1 1 v 1 h 6 v -6 h -1 c -0.550782 0 -1 0.449219 -1 1 v 1.6875 c -1.113282 -1.695312 -3.007813 -2.710937 -5.039063 -2.695312 z m 0 0"/><path d="m 8.035156 15.007812 c 0.082032 0 0.164063 -0.003906 0.246094 -0.007812 c 0.1875 -0.011719 0.375 -0.03125 0.5625 -0.0625 c 1.582031 -0.226562 3.007812 -1.066406 3.96875 -2.34375 c 0.804688 -1.074219 1.183594 -2.332031 1.179688 -3.585938 h -2.003907 c -0.003906 0.832032 -0.257812 1.675782 -0.796875 2.398438 c -1.335937 1.777344 -3.820312 2.113281 -5.597656 0.78125 c -0.429688 -0.320312 -0.769531 -0.734375 -1.03125 -1.1875 h 1.4375 c 0.550781 0 1 -0.449219 1 -1 v -1 h -6 v 6 h 1 c 0.550781 0 1 -0.449219 1 -1 v -1.6875 c 1.113281 1.695312 3.007812 2.710938 5.035156 2.695312 z m 0 0"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
25
src/internal.py
Normal file
25
src/internal.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# internal.py
|
||||||
|
"""
|
||||||
|
Handles paths, they can be different if the app is running as a Flatpak
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
APP_ID = "com.jeffser.Alpaca"
|
||||||
|
|
||||||
|
IN_FLATPAK = bool(os.getenv("FLATPAK_ID"))
|
||||||
|
|
||||||
|
def get_xdg_home(env, default):
|
||||||
|
if IN_FLATPAK:
|
||||||
|
return os.getenv(env)
|
||||||
|
base = os.getenv(env) or os.path.expanduser(default)
|
||||||
|
path = os.path.join(base, APP_ID)
|
||||||
|
if not os.path.exists(path):
|
||||||
|
os.makedirs(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
data_dir = get_xdg_home("XDG_DATA_HOME", "~/.local/share")
|
||||||
|
config_dir = get_xdg_home("XDG_CONFIG_HOME", "~/.config")
|
||||||
|
cache_dir = get_xdg_home("XDG_CACHE_HOME", "~/.cache")
|
||||||
|
|
||||||
|
source_dir = os.path.abspath(os.path.dirname(__file__))
|
||||||
@@ -1,29 +1,35 @@
|
|||||||
# local_instance.py
|
# local_instance.py
|
||||||
import subprocess, os, threading
|
"""
|
||||||
|
Handles running, stopping and resetting the integrated Ollama instance
|
||||||
|
"""
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
from .internal import data_dir, cache_dir
|
||||||
|
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
instance = None
|
instance = None
|
||||||
port = 11435
|
port = 11435
|
||||||
data_dir = os.getenv("XDG_DATA_HOME")
|
|
||||||
overrides = {}
|
overrides = {}
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
if not os.path.isdir(os.path.join(os.getenv("XDG_CACHE_HOME"), 'tmp/ollama')):
|
if not os.path.isdir(os.path.join(cache_dir, 'tmp/ollama')):
|
||||||
os.mkdir(os.path.join(os.getenv("XDG_CACHE_HOME"), 'tmp/ollama'))
|
os.mkdir(os.path.join(cache_dir, 'tmp/ollama'))
|
||||||
global instance, overrides
|
global instance
|
||||||
params = overrides.copy()
|
params = overrides.copy()
|
||||||
params["OLLAMA_HOST"] = f"127.0.0.1:{port}" # You can't change this directly sorry :3
|
params["OLLAMA_HOST"] = f"127.0.0.1:{port}" # You can't change this directly sorry :3
|
||||||
params["HOME"] = data_dir
|
params["HOME"] = data_dir
|
||||||
params["TMPDIR"] = os.path.join(os.getenv("XDG_CACHE_HOME"), 'tmp/ollama')
|
params["TMPDIR"] = os.path.join(cache_dir, 'tmp/ollama')
|
||||||
instance = subprocess.Popen(["/app/bin/ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, text=True)
|
instance = subprocess.Popen(["ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, text=True)
|
||||||
logger.info("Starting Alpaca's Ollama instance...")
|
logger.info("Starting Alpaca's Ollama instance...")
|
||||||
logger.debug(params)
|
logger.debug(params)
|
||||||
sleep(1)
|
sleep(1)
|
||||||
logger.info("Started Alpaca's Ollama instance")
|
logger.info("Started Alpaca's Ollama instance")
|
||||||
|
v_str = subprocess.check_output("ollama -v", shell=True).decode('utf-8')
|
||||||
|
logger.info('Ollama version: {}'.format(v_str.split('client version is ')[1].strip()))
|
||||||
|
|
||||||
def stop():
|
def stop():
|
||||||
logger.info("Stopping Alpaca's Ollama instance")
|
logger.info("Stopping Alpaca's Ollama instance")
|
||||||
@@ -39,4 +45,3 @@ def reset():
|
|||||||
stop()
|
stop()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
start()
|
start()
|
||||||
|
|
||||||
|
|||||||
51
src/main.py
51
src/main.py
@@ -16,21 +16,35 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
"""
|
||||||
|
Main script run at launch, handles actions, about dialog and the app itself (not the window)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '4.0')
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
from gi.repository import Gtk, Gio, Adw, GLib
|
||||||
|
|
||||||
|
from .window import AlpacaWindow
|
||||||
|
from .internal import cache_dir, data_dir
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import gi
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
gi.require_version('Gtk', '4.0')
|
|
||||||
gi.require_version('Adw', '1')
|
|
||||||
|
|
||||||
from gi.repository import Gtk, Gio, Adw, GLib
|
|
||||||
from .window import AlpacaWindow
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
translators = [
|
||||||
|
'Alex K (Russian) https://github.com/alexkdeveloper',
|
||||||
|
'Jeffry Samuel (Spanish) https://github.com/jeffser',
|
||||||
|
'Louis Chauvet-Villaret (French) https://github.com/loulou64490',
|
||||||
|
'Théo FORTIN (French) https://github.com/topiga',
|
||||||
|
'Daimar Stein (Brazilian Portuguese) https://github.com/not-a-dev-stein',
|
||||||
|
'CounterFlow64 (Norwegian) https://github.com/CounterFlow64',
|
||||||
|
'Aritra Saha (Bengali) https://github.com/olumolu',
|
||||||
|
'Yuehao Sui (Simplified Chinese) https://github.com/8ar10der',
|
||||||
|
'Aleksana (Simplified Chinese) https://github.com/Aleksanaa'
|
||||||
|
]
|
||||||
|
|
||||||
class AlpacaApplication(Adw.Application):
|
class AlpacaApplication(Adw.Application):
|
||||||
"""The main application singleton class."""
|
"""The main application singleton class."""
|
||||||
@@ -38,8 +52,8 @@ class AlpacaApplication(Adw.Application):
|
|||||||
def __init__(self, version):
|
def __init__(self, version):
|
||||||
super().__init__(application_id='com.jeffser.Alpaca',
|
super().__init__(application_id='com.jeffser.Alpaca',
|
||||||
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
|
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
|
||||||
self.create_action('quit', lambda *_: self.quit(), ['<primary>q'])
|
self.create_action('quit', lambda *_: self.quit(), ['<primary>w'])
|
||||||
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>p'])
|
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>comma'])
|
||||||
self.create_action('about', self.on_about_action)
|
self.create_action('about', self.on_about_action)
|
||||||
self.version = version
|
self.version = version
|
||||||
|
|
||||||
@@ -58,12 +72,13 @@ class AlpacaApplication(Adw.Application):
|
|||||||
support_url="https://github.com/Jeffser/Alpaca/discussions/155",
|
support_url="https://github.com/Jeffser/Alpaca/discussions/155",
|
||||||
developers=['Jeffser https://jeffser.com'],
|
developers=['Jeffser https://jeffser.com'],
|
||||||
designers=['Jeffser https://jeffser.com', 'Tobias Bernard (App Icon) https://tobiasbernard.com/'],
|
designers=['Jeffser https://jeffser.com', 'Tobias Bernard (App Icon) https://tobiasbernard.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\nLouis Chauvet-Villaret (French) https://github.com/loulou64490\nCounterFlow64 (Norwegian) https://github.com/CounterFlow64\nAritra Saha (Bengali) https://github.com/olumolu\nYuehao Sui (Simplified Chinese) https://github.com/8ar10der',
|
translator_credits='\n'.join(translators),
|
||||||
copyright='© 2024 Jeffser\n© 2024 Ollama',
|
copyright='© 2024 Jeffser\n© 2024 Ollama',
|
||||||
issue_url='https://github.com/Jeffser/Alpaca/issues',
|
issue_url='https://github.com/Jeffser/Alpaca/issues',
|
||||||
license_type=3,
|
license_type=3,
|
||||||
website="https://jeffser.com/alpaca",
|
website="https://jeffser.com/alpaca",
|
||||||
debug_info=open(os.path.join(os.getenv("XDG_DATA_HOME"), 'tmp.log'), 'r').read())
|
debug_info=open(os.path.join(data_dir, 'tmp.log'), 'r').read())
|
||||||
|
about.add_link("Become a Sponsor", "https://github.com/sponsors/Jeffser")
|
||||||
about.present(parent=self.props.active_window)
|
about.present(parent=self.props.active_window)
|
||||||
|
|
||||||
def create_action(self, name, callback, shortcuts=None):
|
def create_action(self, name, callback, shortcuts=None):
|
||||||
@@ -75,16 +90,16 @@ class AlpacaApplication(Adw.Application):
|
|||||||
|
|
||||||
|
|
||||||
def main(version):
|
def main(version):
|
||||||
if os.path.isfile(os.path.join(os.getenv("XDG_DATA_HOME"), 'tmp.log')):
|
if os.path.isfile(os.path.join(data_dir, 'tmp.log')):
|
||||||
os.remove(os.path.join(os.getenv("XDG_DATA_HOME"), 'tmp.log'))
|
os.remove(os.path.join(data_dir, 'tmp.log'))
|
||||||
if os.path.isdir(os.path.join(os.getenv("XDG_CACHE_HOME"), 'tmp')):
|
if os.path.isdir(os.path.join(cache_dir, 'tmp')):
|
||||||
os.system('rm -rf ' + os.path.join(os.getenv("XDG_CACHE_HOME"), "tmp/*"))
|
os.system('rm -rf ' + os.path.join(cache_dir, "tmp/*"))
|
||||||
else:
|
else:
|
||||||
os.mkdir(os.path.join(os.getenv("XDG_CACHE_HOME"), 'tmp'))
|
os.mkdir(os.path.join(cache_dir, 'tmp'))
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format="%(levelname)s\t[%(filename)s | %(funcName)s] %(message)s",
|
format="%(levelname)s\t[%(filename)s | %(funcName)s] %(message)s",
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
handlers=[logging.FileHandler(filename=os.path.join(os.getenv("XDG_DATA_HOME"), 'tmp.log')), logging.StreamHandler(stream=sys.stdout)]
|
handlers=[logging.FileHandler(filename=os.path.join(data_dir, 'tmp.log')), logging.StreamHandler(stream=sys.stdout)]
|
||||||
)
|
)
|
||||||
app = AlpacaApplication(version)
|
app = AlpacaApplication(version)
|
||||||
logger.info(f"Alpaca version: {app.version}")
|
logger.info(f"Alpaca version: {app.version}")
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ alpaca_sources = [
|
|||||||
'local_instance.py',
|
'local_instance.py',
|
||||||
'available_models.json',
|
'available_models.json',
|
||||||
'available_models_descriptions.py',
|
'available_models_descriptions.py',
|
||||||
'table_widget.py'
|
'table_widget.py',
|
||||||
|
'internal.py'
|
||||||
]
|
]
|
||||||
|
|
||||||
install_data(alpaca_sources, install_dir: moduledir)
|
install_data(alpaca_sources, install_dir: moduledir)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
}
|
}
|
||||||
.message_text_view {
|
.message_text_view, .modelfile_textview {
|
||||||
background-color: rgba(0,0,0,0);
|
background-color: rgba(0,0,0,0);
|
||||||
}
|
}
|
||||||
.chat_image_button {
|
.chat_image_button {
|
||||||
@@ -12,7 +12,3 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
.chat_row:selected {
|
|
||||||
background: mix(@theme_bg_color, @theme_selected_bg_color, 0.3);
|
|
||||||
color: mix(@window_fg_color, @theme_selected_bg_color, 0.5);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
#table_widget.py
|
||||||
|
"""
|
||||||
|
Handles the table widget shown in chat responses
|
||||||
|
"""
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
from gi.repository import Adw
|
gi.require_version('Gtk', '4.0')
|
||||||
from gi.repository import Gtk, GObject, Gio
|
from gi.repository import Gtk, GObject, Gio
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|||||||
485
src/window.py
485
src/window.py
@@ -16,33 +16,37 @@
|
|||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
"""
|
||||||
import gi
|
Handles the main window
|
||||||
gi.require_version('GtkSource', '5')
|
"""
|
||||||
gi.require_version('GdkPixbuf', '2.0')
|
import json, threading, os, re, base64, sys, gettext, uuid, shutil, tarfile, tempfile, logging
|
||||||
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
|
||||||
import json, requests, threading, os, re, base64, sys, gettext, locale, subprocess, uuid, shutil, tarfile, tempfile, logging, random
|
|
||||||
from time import sleep
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from pypdf import PdfReader
|
from pypdf import PdfReader
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version('GtkSource', '5')
|
||||||
|
gi.require_version('GdkPixbuf', '2.0')
|
||||||
|
|
||||||
|
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
||||||
|
|
||||||
from . import dialogs, local_instance, connection_handler, available_models_descriptions
|
from . import dialogs, local_instance, connection_handler, available_models_descriptions
|
||||||
from .table_widget import TableWidget
|
from .table_widget import TableWidget
|
||||||
|
from .internal import config_dir, data_dir, cache_dir, source_dir
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
|
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
|
||||||
class AlpacaWindow(Adw.ApplicationWindow):
|
class AlpacaWindow(Adw.ApplicationWindow):
|
||||||
config_dir = os.getenv("XDG_CONFIG_HOME")
|
|
||||||
data_dir = os.getenv("XDG_DATA_HOME")
|
|
||||||
app_dir = os.getenv("FLATPAK_DEST")
|
app_dir = os.getenv("FLATPAK_DEST")
|
||||||
cache_dir = os.getenv("XDG_CACHE_HOME")
|
config_dir = config_dir
|
||||||
|
data_dir = data_dir
|
||||||
|
cache_dir = cache_dir
|
||||||
|
|
||||||
__gtype_name__ = 'AlpacaWindow'
|
__gtype_name__ = 'AlpacaWindow'
|
||||||
|
|
||||||
localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale')
|
localedir = os.path.join(source_dir, 'locale')
|
||||||
|
|
||||||
gettext.bindtextdomain('com.jeffser.Alpaca', localedir)
|
gettext.bindtextdomain('com.jeffser.Alpaca', localedir)
|
||||||
gettext.textdomain('com.jeffser.Alpaca')
|
gettext.textdomain('com.jeffser.Alpaca')
|
||||||
@@ -60,7 +64,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
pulling_models = {}
|
pulling_models = {}
|
||||||
chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": "New Chat", "order": []}
|
chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": "New Chat", "order": []}
|
||||||
attachments = {}
|
attachments = {}
|
||||||
show_support = True
|
|
||||||
|
|
||||||
#Override elements
|
#Override elements
|
||||||
override_HSA_OVERRIDE_GFX_VERSION = Gtk.Template.Child()
|
override_HSA_OVERRIDE_GFX_VERSION = Gtk.Template.Child()
|
||||||
@@ -68,11 +71,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
override_HIP_VISIBLE_DEVICES = Gtk.Template.Child()
|
override_HIP_VISIBLE_DEVICES = Gtk.Template.Child()
|
||||||
|
|
||||||
#Elements
|
#Elements
|
||||||
|
split_view_overlay = Gtk.Template.Child()
|
||||||
|
regenerate_button : Gtk.Button = None
|
||||||
|
selected_chat_row : Gtk.ListBoxRow = None
|
||||||
create_model_base = Gtk.Template.Child()
|
create_model_base = Gtk.Template.Child()
|
||||||
create_model_name = Gtk.Template.Child()
|
create_model_name = Gtk.Template.Child()
|
||||||
create_model_system = Gtk.Template.Child()
|
create_model_system = Gtk.Template.Child()
|
||||||
create_model_template = Gtk.Template.Child()
|
create_model_modelfile = Gtk.Template.Child()
|
||||||
create_model_dialog = Gtk.Template.Child()
|
|
||||||
temperature_spin = Gtk.Template.Child()
|
temperature_spin = Gtk.Template.Child()
|
||||||
seed_spin = Gtk.Template.Child()
|
seed_spin = Gtk.Template.Child()
|
||||||
keep_alive_spin = Gtk.Template.Child()
|
keep_alive_spin = Gtk.Template.Child()
|
||||||
@@ -134,14 +139,14 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def verify_if_image_can_be_used(self, pspec=None, user_data=None):
|
def verify_if_image_can_be_used(self, pspec=None, user_data=None):
|
||||||
logger.debug("Verifying if image can be used")
|
logger.debug("Verifying if image can be used")
|
||||||
if self.model_drop_down.get_selected_item() == None: return True
|
if self.model_drop_down.get_selected_item() == None:
|
||||||
selected = self.model_drop_down.get_selected_item().get_string().split(" (")[0].lower()
|
return True
|
||||||
|
selected = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1).split(":")[0]
|
||||||
if selected in [key for key, value in self.available_models.items() if value["image"]]:
|
if selected in [key for key, value in self.available_models.items() if value["image"]]:
|
||||||
for name, content in self.attachments.items():
|
for name, content in self.attachments.items():
|
||||||
if content['type'] == 'image':
|
if content['type'] == 'image':
|
||||||
content['button'].set_css_classes(["flat"])
|
content['button'].set_css_classes(["flat"])
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
for name, content in self.attachments.items():
|
for name, content in self.attachments.items():
|
||||||
if content['type'] == 'image':
|
if content['type'] == 'image':
|
||||||
content['button'].set_css_classes(["flat", "error"])
|
content['button'].set_css_classes(["flat", "error"])
|
||||||
@@ -149,7 +154,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def stop_message(self, button=None):
|
def stop_message(self, button=None):
|
||||||
if self.loading_spinner: self.chat_container.remove(self.loading_spinner)
|
if self.loading_spinner:
|
||||||
|
self.chat_container.remove(self.loading_spinner)
|
||||||
self.toggle_ui_sensitive(True)
|
self.toggle_ui_sensitive(True)
|
||||||
self.switch_send_stop_button()
|
self.switch_send_stop_button()
|
||||||
self.bot_message = None
|
self.bot_message = None
|
||||||
@@ -173,8 +179,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.save_history()
|
self.save_history()
|
||||||
self.show_toast(_("Message edited successfully"), self.main_overlay)
|
self.show_toast(_("Message edited successfully"), self.main_overlay)
|
||||||
|
|
||||||
if self.bot_message or self.get_focus() not in (self.message_text_view, self.send_button): return
|
if self.bot_message or self.get_focus() not in (self.message_text_view, self.send_button):
|
||||||
if not self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False): return
|
return
|
||||||
|
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_chat_row = self.chat_list_box.get_selected_row()
|
current_chat_row = self.chat_list_box.get_selected_row()
|
||||||
self.chat_list_box.unselect_all()
|
self.chat_list_box.unselect_all()
|
||||||
self.chat_list_box.remove(current_chat_row)
|
self.chat_list_box.remove(current_chat_row)
|
||||||
@@ -183,23 +191,23 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.chats['order'].remove(self.chats['selected_chat'])
|
self.chats['order'].remove(self.chats['selected_chat'])
|
||||||
self.chats['order'].insert(0, self.chats['selected_chat'])
|
self.chats['order'].insert(0, self.chats['selected_chat'])
|
||||||
self.save_history()
|
self.save_history()
|
||||||
current_model = self.model_drop_down.get_selected_item().get_string().split(' (')
|
current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
|
||||||
current_model = '{}:{}'.format(current_model[0].replace(' ', '-').lower(), current_model[1][:-1])
|
|
||||||
if current_model is None:
|
if current_model is None:
|
||||||
self.show_toast(_("Please select a model before chatting"), self.main_overlay)
|
self.show_toast(_("Please select a model before chatting"), self.main_overlay)
|
||||||
return
|
return
|
||||||
id = self.generate_uuid()
|
message_id = self.generate_uuid()
|
||||||
|
|
||||||
attached_images = []
|
attached_images = []
|
||||||
attached_files = {}
|
attached_files = {}
|
||||||
can_use_images = self.verify_if_image_can_be_used()
|
can_use_images = self.verify_if_image_can_be_used()
|
||||||
for name, content in self.attachments.items():
|
for name, content in self.attachments.items():
|
||||||
if content["type"] == 'image' and can_use_images: attached_images.append(name)
|
if content["type"] == 'image' and can_use_images:
|
||||||
|
attached_images.append(name)
|
||||||
else:
|
else:
|
||||||
attached_files[name] = content['type']
|
attached_files[name] = content['type']
|
||||||
if not os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id)):
|
if not os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id)):
|
||||||
os.makedirs(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id))
|
os.makedirs(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id))
|
||||||
shutil.copy(content['path'], os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name))
|
shutil.copy(content['path'], os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id, name))
|
||||||
content["button"].get_parent().remove(content["button"])
|
content["button"].get_parent().remove(content["button"])
|
||||||
self.attachments = {}
|
self.attachments = {}
|
||||||
self.attachment_box.set_visible(False)
|
self.attachment_box.set_visible(False)
|
||||||
@@ -208,16 +216,16 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
current_datetime = datetime.now()
|
current_datetime = datetime.now()
|
||||||
|
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id] = {
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"model": "User",
|
"model": "User",
|
||||||
"date": current_datetime.strftime("%Y/%m/%d %H:%M:%S"),
|
"date": current_datetime.strftime("%Y/%m/%d %H:%M:%S"),
|
||||||
"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)
|
"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)
|
||||||
}
|
}
|
||||||
if len(attached_images) > 0:
|
if len(attached_images) > 0:
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['images'] = attached_images
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['images'] = attached_images
|
||||||
if len(attached_files.keys()) > 0:
|
if len(attached_files.keys()) > 0:
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['files'] = attached_files
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['files'] = attached_files
|
||||||
data = {
|
data = {
|
||||||
"model": current_model,
|
"model": current_model,
|
||||||
"messages": self.convert_history_to_ollama(),
|
"messages": self.convert_history_to_ollama(),
|
||||||
@@ -230,12 +238,12 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
#self.attachments[name] = {"path": file_path, "type": file_type, "content": content}
|
#self.attachments[name] = {"path": file_path, "type": file_type, "content": content}
|
||||||
raw_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)
|
raw_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)
|
||||||
formated_date = GLib.markup_escape_text(self.generate_datetime_format(current_datetime))
|
formated_date = GLib.markup_escape_text(self.generate_datetime_format(current_datetime))
|
||||||
self.show_message(raw_message, False, f"\n\n<small>{formated_date}</small>", attached_images, attached_files, id=id)
|
self.show_message(raw_message, False, f"\n\n<small>{formated_date}</small>", attached_images, attached_files, message_id=message_id)
|
||||||
self.message_text_view.get_buffer().set_text("", 0)
|
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.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
|
||||||
self.chat_container.append(self.loading_spinner)
|
self.chat_container.append(self.loading_spinner)
|
||||||
bot_id=self.generate_uuid()
|
bot_id=self.generate_uuid()
|
||||||
self.show_message("", True, id=bot_id)
|
self.show_message("", True, message_id=bot_id)
|
||||||
|
|
||||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], bot_id))
|
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], bot_id))
|
||||||
thread.start()
|
thread.start()
|
||||||
@@ -245,17 +253,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
generate_title_thread = threading.Thread(target=self.generate_chat_title, args=(message_data, self.chat_list_box.get_selected_row().get_child()))
|
generate_title_thread = threading.Thread(target=self.generate_chat_title, args=(message_data, self.chat_list_box.get_selected_row().get_child()))
|
||||||
generate_title_thread.start()
|
generate_title_thread.start()
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
|
||||||
def manage_models_button_activate(self, button=None):
|
|
||||||
logger.debug(f"Managing models")
|
|
||||||
self.update_list_local_models()
|
|
||||||
self.manage_models_dialog.present(self)
|
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def welcome_carousel_page_changed(self, carousel, index):
|
def welcome_carousel_page_changed(self, carousel, index):
|
||||||
logger.debug("Showing welcome carousel")
|
logger.debug("Showing welcome carousel")
|
||||||
if index == 0: self.welcome_previous_button.set_sensitive(False)
|
if index == 0:
|
||||||
else: self.welcome_previous_button.set_sensitive(True)
|
self.welcome_previous_button.set_sensitive(False)
|
||||||
|
else:
|
||||||
|
self.welcome_previous_button.set_sensitive(True)
|
||||||
if index == carousel.get_n_pages()-1:
|
if index == carousel.get_n_pages()-1:
|
||||||
self.welcome_next_button.set_label(_("Close"))
|
self.welcome_next_button.set_label(_("Close"))
|
||||||
self.welcome_next_button.set_tooltip_text(_("Close"))
|
self.welcome_next_button.set_tooltip_text(_("Close"))
|
||||||
@@ -269,7 +273,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def welcome_next_button_activate(self, button):
|
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)
|
if button.get_label() == "Next":
|
||||||
|
self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()+1), True)
|
||||||
else:
|
else:
|
||||||
self.welcome_dialog.force_close()
|
self.welcome_dialog.force_close()
|
||||||
if not self.verify_connection():
|
if not self.verify_connection():
|
||||||
@@ -282,8 +287,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.chats["selected_chat"] = row.get_child().get_name()
|
self.chats["selected_chat"] = row.get_child().get_name()
|
||||||
self.load_history_into_chat()
|
self.load_history_into_chat()
|
||||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
||||||
|
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
|
||||||
|
last_model_used = self.convert_model_name(last_model_used, 0)
|
||||||
for i in range(self.model_string_list.get_n_items()):
|
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"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]:
|
if self.model_string_list.get_string(i) == last_model_used:
|
||||||
self.model_drop_down.set_selected(i)
|
self.model_drop_down.set_selected(i)
|
||||||
break
|
break
|
||||||
self.save_history()
|
self.save_history()
|
||||||
@@ -335,27 +342,28 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def model_spin_changed(self, spin):
|
def model_spin_changed(self, spin):
|
||||||
value = spin.get_value()
|
value = spin.get_value()
|
||||||
if spin.get_name() != "temperature": value = round(value)
|
if spin.get_name() != "temperature":
|
||||||
else: value = round(value, 1)
|
value = round(value)
|
||||||
|
else:
|
||||||
|
value = round(value, 1)
|
||||||
if self.model_tweaks[spin.get_name()] is not None and self.model_tweaks[spin.get_name()] != value:
|
if self.model_tweaks[spin.get_name()] is not None and self.model_tweaks[spin.get_name()] != value:
|
||||||
self.model_tweaks[spin.get_name()] = value
|
self.model_tweaks[spin.get_name()] = value
|
||||||
self.save_server_config()
|
self.save_server_config()
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def create_model_start(self, button):
|
def create_model_start(self, button):
|
||||||
base = self.create_model_base.get_subtitle()
|
name = self.create_model_name.get_text().lower().replace(":", "")
|
||||||
name = self.create_model_name.get_text()
|
modelfile_buffer = self.create_model_modelfile.get_buffer()
|
||||||
system = self.create_model_system.get_text()
|
modelfile_raw = modelfile_buffer.get_text(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter(), False)
|
||||||
template = self.create_model_template.get_text()
|
modelfile = ["FROM {}".format(self.create_model_base.get_subtitle()), "SYSTEM {}".format(self.create_model_system.get_text())]
|
||||||
if "/" in base:
|
for line in modelfile_raw.split('\n'):
|
||||||
modelfile = f"FROM {base}\nSYSTEM {system}\nTEMPLATE {template}"
|
if not line.startswith('SYSTEM') and not line.startswith('FROM'):
|
||||||
else:
|
modelfile.append(line)
|
||||||
modelfile = f"FROM {base}\nSYSTEM {system}"
|
|
||||||
self.pulling_model_list_box.set_visible(True)
|
self.pulling_model_list_box.set_visible(True)
|
||||||
model_row = Adw.ActionRow(
|
model_row = Adw.ActionRow(
|
||||||
title = name
|
title = name
|
||||||
)
|
)
|
||||||
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": name, "modelfile": modelfile})
|
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": name, "modelfile": '\n'.join(modelfile)})
|
||||||
overlay = Gtk.Overlay()
|
overlay = Gtk.Overlay()
|
||||||
progress_bar = Gtk.ProgressBar(
|
progress_bar = Gtk.ProgressBar(
|
||||||
valign = 2,
|
valign = 2,
|
||||||
@@ -377,19 +385,22 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
overlay.set_child(model_row)
|
overlay.set_child(model_row)
|
||||||
overlay.add_overlay(progress_bar)
|
overlay.add_overlay(progress_bar)
|
||||||
self.pulling_model_list_box.append(overlay)
|
self.pulling_model_list_box.append(overlay)
|
||||||
self.create_model_dialog.close()
|
self.navigation_view_manage_models.pop()
|
||||||
self.manage_models_dialog.present(self)
|
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def override_changed(self, entry):
|
def override_changed(self, entry):
|
||||||
name = entry.get_name()
|
name = entry.get_name()
|
||||||
value = entry.get_text()
|
value = entry.get_text()
|
||||||
if (not value and name not in local_instance.overrides) or (value and value in local_instance.overrides and local_instance.overrides[name] == value): return
|
if (not value and name not in local_instance.overrides) or (value and value in local_instance.overrides and local_instance.overrides[name] == value):
|
||||||
if not value: del local_instance.overrides[name]
|
return
|
||||||
else: local_instance.overrides[name] = value
|
if not value:
|
||||||
|
del local_instance.overrides[name]
|
||||||
|
else:
|
||||||
|
local_instance.overrides[name] = value
|
||||||
self.save_server_config()
|
self.save_server_config()
|
||||||
if not self.run_remote: local_instance.reset()
|
if not self.run_remote:
|
||||||
|
local_instance.reset()
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def link_button_handler(self, button):
|
def link_button_handler(self, button):
|
||||||
@@ -407,7 +418,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
for i, key in enumerate(self.available_models.keys()):
|
for i, key in enumerate(self.available_models.keys()):
|
||||||
row = self.available_model_list_box.get_row_at_index(i)
|
row = self.available_model_list_box.get_row_at_index(i)
|
||||||
row.set_visible(re.search(entry.get_text(), '{} {} {}'.format(row.get_title(), (_("image") if self.available_models[key]['image'] else " "), row.get_subtitle()), re.IGNORECASE))
|
row.set_visible(re.search(entry.get_text(), '{} {} {}'.format(row.get_title(), (_("image") if self.available_models[key]['image'] else " "), row.get_subtitle()), re.IGNORECASE))
|
||||||
if row.get_visible(): results += 1
|
if row.get_visible():
|
||||||
|
results += 1
|
||||||
if entry.get_text() and results == 0:
|
if entry.get_text() and results == 0:
|
||||||
self.available_model_list_box.set_visible(False)
|
self.available_model_list_box.set_visible(False)
|
||||||
self.no_results_page.set_visible(True)
|
self.no_results_page.set_visible(True)
|
||||||
@@ -415,38 +427,53 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.available_model_list_box.set_visible(True)
|
self.available_model_list_box.set_visible(True)
|
||||||
self.no_results_page.set_visible(False)
|
self.no_results_page.set_visible(False)
|
||||||
|
|
||||||
|
def manage_models_button_activate(self, button=None):
|
||||||
|
logger.debug(f"Managing models")
|
||||||
|
self.update_list_local_models()
|
||||||
|
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
||||||
|
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
|
||||||
|
last_model_used = self.convert_model_name(last_model_used, 0)
|
||||||
|
for i in range(self.model_string_list.get_n_items()):
|
||||||
|
if self.model_string_list.get_string(i) == last_model_used:
|
||||||
|
self.model_drop_down.set_selected(i)
|
||||||
|
break
|
||||||
|
self.manage_models_dialog.present(self)
|
||||||
|
|
||||||
|
def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag
|
||||||
|
if mode == 0:
|
||||||
|
return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
|
||||||
|
if mode == 1:
|
||||||
|
return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-1])
|
||||||
|
|
||||||
def check_alphanumeric(self, editable, text, length, position):
|
def check_alphanumeric(self, editable, text, length, position):
|
||||||
new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '_']])
|
new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '.', ':', '_']])
|
||||||
if new_text != text: editable.stop_emission_by_name("insert-text")
|
if new_text != text:
|
||||||
|
editable.stop_emission_by_name("insert-text")
|
||||||
|
|
||||||
def create_model(self, model:str, file:bool):
|
def create_model(self, model:str, file:bool):
|
||||||
name = ""
|
modelfile_buffer = self.create_model_modelfile.get_buffer()
|
||||||
system = ""
|
modelfile_buffer.delete(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter())
|
||||||
template = ""
|
self.create_model_system.set_text('')
|
||||||
if not file:
|
if not file:
|
||||||
response = connection_handler.simple_post(f"{connection_handler.url}/api/show", json.dumps({"name": model}))
|
response = connection_handler.simple_post(f"{connection_handler.url}/api/show", json.dumps({"name": self.convert_model_name(model, 1)}))
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = json.loads(response.text)
|
data = json.loads(response.text)
|
||||||
|
modelfile = []
|
||||||
for line in data['modelfile'].split('\n'):
|
for line in data['modelfile'].split('\n'):
|
||||||
if line.startswith('SYSTEM'):
|
if line.startswith('SYSTEM'):
|
||||||
system = line[len('SYSTEM'):].strip()
|
self.create_model_system.set_text(line[len('SYSTEM'):].strip())
|
||||||
elif line.startswith('TEMPLATE'):
|
if not line.startswith('SYSTEM') and not line.startswith('FROM') and not line.startswith('#'):
|
||||||
template = line[len('TEMPLATE'):].strip()
|
modelfile.append(line)
|
||||||
self.create_model_template.set_sensitive(False)
|
self.create_model_name.set_text(self.convert_model_name(model, 1).split(':')[0] + "-custom")
|
||||||
name = model.split(':')[0]
|
modelfile_buffer.insert(modelfile_buffer.get_start_iter(), '\n'.join(modelfile), len('\n'.join(modelfile).encode('utf-8')))
|
||||||
else:
|
else:
|
||||||
self.create_model_template.set_sensitive(True)
|
##TODO ERROR MESSAGE
|
||||||
template = '"""{{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n{{ .Response }}<|eot_id|>"""'
|
return
|
||||||
name = model.split("/")[-1].split(".")[0]
|
self.create_model_base.set_subtitle(self.convert_model_name(model, 1))
|
||||||
|
else:
|
||||||
|
self.create_model_name.set_text(os.path.splitext(os.path.basename(model))[0])
|
||||||
self.create_model_base.set_subtitle(model)
|
self.create_model_base.set_subtitle(model)
|
||||||
self.create_model_name.set_text(name)
|
self.navigation_view_manage_models.push_by_tag('model_create_page')
|
||||||
self.create_model_system.set_text(system)
|
|
||||||
self.create_model_template.set_text(template)
|
|
||||||
self.manage_models_dialog.close()
|
|
||||||
self.create_model_dialog.present(self)
|
|
||||||
|
|
||||||
|
|
||||||
def show_toast(self, message:str, overlay):
|
def show_toast(self, message:str, overlay):
|
||||||
logger.info(message)
|
logger.info(message)
|
||||||
@@ -461,31 +488,33 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
logger.info(f"{title}, {body}")
|
logger.info(f"{title}, {body}")
|
||||||
notification = Gio.Notification.new(title)
|
notification = Gio.Notification.new(title)
|
||||||
notification.set_body(body)
|
notification.set_body(body)
|
||||||
if icon: notification.set_icon(icon)
|
if icon:
|
||||||
|
notification.set_icon(icon)
|
||||||
self.get_application().send_notification(None, notification)
|
self.get_application().send_notification(None, notification)
|
||||||
|
|
||||||
def delete_message(self, message_element):
|
def delete_message(self, message_element):
|
||||||
logger.debug("Deleting message")
|
logger.debug("Deleting message")
|
||||||
id = message_element.get_name()
|
message_id = message_element.get_name()
|
||||||
del self.chats["chats"][self.chats["selected_chat"]]["messages"][id]
|
del self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]
|
||||||
self.chat_container.remove(message_element)
|
self.chat_container.remove(message_element)
|
||||||
if os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id)):
|
if os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id)):
|
||||||
shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id))
|
shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id))
|
||||||
self.save_history()
|
self.save_history()
|
||||||
|
|
||||||
def copy_message(self, message_element):
|
def copy_message(self, message_element):
|
||||||
logger.debug("Copying message")
|
logger.debug("Copying message")
|
||||||
id = message_element.get_name()
|
message_id = message_element.get_name()
|
||||||
clipboard = Gdk.Display().get_default().get_clipboard()
|
clipboard = Gdk.Display().get_default().get_clipboard()
|
||||||
clipboard.set(self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["content"])
|
clipboard.set(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["content"])
|
||||||
self.show_toast(_("Message copied to the clipboard"), self.main_overlay)
|
self.show_toast(_("Message copied to the clipboard"), self.main_overlay)
|
||||||
|
|
||||||
def edit_message(self, message_element, text_view, button_container):
|
def edit_message(self, message_element, text_view, button_container):
|
||||||
logger.debug("Editing message")
|
logger.debug("Editing message")
|
||||||
if self.editing_message: self.send_message()
|
if self.editing_message:
|
||||||
|
self.send_message()
|
||||||
|
|
||||||
button_container.set_visible(False)
|
button_container.set_visible(False)
|
||||||
id = message_element.get_name()
|
message_id = message_element.get_name()
|
||||||
|
|
||||||
text_buffer = text_view.get_buffer()
|
text_buffer = text_view.get_buffer()
|
||||||
end_iter = text_buffer.get_end_iter()
|
end_iter = text_buffer.get_end_iter()
|
||||||
@@ -499,7 +528,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
text_view.set_css_classes(["view", "editing_message_textview"])
|
text_view.set_css_classes(["view", "editing_message_textview"])
|
||||||
text_view.set_cursor_visible(True)
|
text_view.set_cursor_visible(True)
|
||||||
|
|
||||||
self.editing_message = {"text_view": text_view, "id": id, "button_container": button_container, "footer": footer}
|
self.editing_message = {"text_view": text_view, "id": message_id, "button_container": button_container, "footer": footer}
|
||||||
|
|
||||||
def preview_file(self, file_path, file_type, presend_name):
|
def preview_file(self, file_path, file_type, presend_name):
|
||||||
logger.debug(f"Previewing file: {file_path}")
|
logger.debug(f"Previewing file: {file_path}")
|
||||||
@@ -542,22 +571,24 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
def convert_history_to_ollama(self):
|
def convert_history_to_ollama(self):
|
||||||
messages = []
|
messages = []
|
||||||
for id, message in self.chats["chats"][self.chats["selected_chat"]]["messages"].items():
|
for message_id, message in self.chats["chats"][self.chats["selected_chat"]]["messages"].items():
|
||||||
new_message = message.copy()
|
new_message = message.copy()
|
||||||
if 'files' in message and len(message['files']) > 0:
|
if 'files' in message and len(message['files']) > 0:
|
||||||
del new_message['files']
|
del new_message['files']
|
||||||
new_message['content'] = ''
|
new_message['content'] = ''
|
||||||
for name, file_type in message['files'].items():
|
for name, file_type in message['files'].items():
|
||||||
file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name)
|
file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id, name)
|
||||||
file_data = self.get_content_of_file(file_path, file_type)
|
file_data = self.get_content_of_file(file_path, file_type)
|
||||||
if file_data: new_message['content'] += f"```[{name}]\n{file_data}\n```"
|
if file_data:
|
||||||
|
new_message['content'] += f"```[{name}]\n{file_data}\n```"
|
||||||
new_message['content'] += message['content']
|
new_message['content'] += message['content']
|
||||||
if 'images' in message and len(message['images']) > 0:
|
if 'images' in message and len(message['images']) > 0:
|
||||||
new_message['images'] = []
|
new_message['images'] = []
|
||||||
for name in message['images']:
|
for name in message['images']:
|
||||||
file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name)
|
file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id, name)
|
||||||
image_data = self.get_content_of_file(file_path, 'image')
|
image_data = self.get_content_of_file(file_path, 'image')
|
||||||
if image_data: new_message['images'].append(image_data)
|
if image_data:
|
||||||
|
new_message['images'].append(image_data)
|
||||||
messages.append(new_message)
|
messages.append(new_message)
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
@@ -574,17 +605,17 @@ Generate a title following these rules:
|
|||||||
```PROMPT
|
```PROMPT
|
||||||
{message['content']}
|
{message['content']}
|
||||||
```"""
|
```"""
|
||||||
current_model = self.model_drop_down.get_selected_item().get_string().split(' (')
|
current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
|
||||||
current_model = '{}:{}'.format(current_model[0].replace(' ', '-').lower(), current_model[1][:-1])
|
|
||||||
data = {"model": current_model, "prompt": prompt, "stream": False}
|
data = {"model": current_model, "prompt": prompt, "stream": False}
|
||||||
if 'images' in message: data["images"] = message['images']
|
if 'images' in message:
|
||||||
|
data["images"] = message['images']
|
||||||
response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data))
|
response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data))
|
||||||
|
|
||||||
new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title()
|
new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title().replace('\'S', '\'s')
|
||||||
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
|
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
|
||||||
self.rename_chat(label_element.get_name(), new_chat_name, label_element)
|
self.rename_chat(label_element.get_name(), new_chat_name, label_element)
|
||||||
|
|
||||||
def show_message(self, msg:str, bot:bool, footer:str=None, images:list=None, files:dict=None, id:str=None):
|
def show_message(self, msg:str, bot:bool, footer:str=None, images:list=None, files:dict=None, message_id:str=None):
|
||||||
message_text = Gtk.TextView(
|
message_text = Gtk.TextView(
|
||||||
editable=False,
|
editable=False,
|
||||||
focusable=True,
|
focusable=True,
|
||||||
@@ -599,7 +630,8 @@ Generate a title following these rules:
|
|||||||
)
|
)
|
||||||
message_buffer = message_text.get_buffer()
|
message_buffer = message_text.get_buffer()
|
||||||
message_buffer.insert(message_buffer.get_end_iter(), msg)
|
message_buffer.insert(message_buffer.get_end_iter(), msg)
|
||||||
if footer is not None: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
|
if footer is not None:
|
||||||
|
message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
|
||||||
|
|
||||||
delete_button = Gtk.Button(
|
delete_button = Gtk.Button(
|
||||||
icon_name = "user-trash-symbolic",
|
icon_name = "user-trash-symbolic",
|
||||||
@@ -616,6 +648,11 @@ Generate a title following these rules:
|
|||||||
css_classes = ["flat", "circular"],
|
css_classes = ["flat", "circular"],
|
||||||
tooltip_text = _("Edit Message")
|
tooltip_text = _("Edit Message")
|
||||||
)
|
)
|
||||||
|
regenerate_button = Gtk.Button(
|
||||||
|
icon_name = "update-symbolic",
|
||||||
|
css_classes = ["flat", "circular"],
|
||||||
|
tooltip_text = _("Regenerate Message")
|
||||||
|
)
|
||||||
|
|
||||||
button_container = Gtk.Box(
|
button_container = Gtk.Box(
|
||||||
orientation=0,
|
orientation=0,
|
||||||
@@ -647,7 +684,7 @@ Generate a title following these rules:
|
|||||||
child=image_container
|
child=image_container
|
||||||
)
|
)
|
||||||
for image in images:
|
for image in images:
|
||||||
path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, image)
|
path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id, image)
|
||||||
try:
|
try:
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
raise FileNotFoundError("'{}' was not found or is a directory".format(path))
|
raise FileNotFoundError("'{}' was not found or is a directory".format(path))
|
||||||
@@ -656,8 +693,8 @@ Generate a title following these rules:
|
|||||||
button = Gtk.Button(
|
button = Gtk.Button(
|
||||||
child=image_element,
|
child=image_element,
|
||||||
css_classes=["flat", "chat_image_button"],
|
css_classes=["flat", "chat_image_button"],
|
||||||
name=os.path.join(self.data_dir, "chats", "{selected_chat}", id, image),
|
name=os.path.join(self.data_dir, "chats", "{selected_chat}", message_id, image),
|
||||||
tooltip_text=os.path.basename(path)
|
tooltip_text=_("Image")
|
||||||
)
|
)
|
||||||
button.connect("clicked", lambda button, file_path=path: self.preview_file(file_path, 'image', None))
|
button.connect("clicked", lambda button, file_path=path: self.preview_file(file_path, 'image', None))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -683,7 +720,7 @@ Generate a title following these rules:
|
|||||||
button = Gtk.Button(
|
button = Gtk.Button(
|
||||||
child=image_box,
|
child=image_box,
|
||||||
css_classes=["flat", "chat_image_button"],
|
css_classes=["flat", "chat_image_button"],
|
||||||
tooltip_text=_("Missing image")
|
tooltip_text=_("Missing Image")
|
||||||
)
|
)
|
||||||
button.connect("clicked", lambda button : self.show_toast(_("Missing image"), self.main_overlay))
|
button.connect("clicked", lambda button : self.show_toast(_("Missing image"), self.main_overlay))
|
||||||
image_container.append(button)
|
image_container.append(button)
|
||||||
@@ -719,21 +756,22 @@ Generate a title following these rules:
|
|||||||
tooltip_text=name,
|
tooltip_text=name,
|
||||||
child=button_content
|
child=button_content
|
||||||
)
|
)
|
||||||
file_path = os.path.join(self.data_dir, "chats", "{selected_chat}", id, name)
|
file_path = os.path.join(self.data_dir, "chats", "{selected_chat}", message_id, name)
|
||||||
button.connect("clicked", lambda button, file_path=file_path, file_type=file_type: self.preview_file(file_path, file_type, None))
|
button.connect("clicked", lambda button, file_path=file_path, file_type=file_type: self.preview_file(file_path, file_type, None))
|
||||||
file_container.append(button)
|
file_container.append(button)
|
||||||
message_box.append(file_scroller)
|
message_box.append(file_scroller)
|
||||||
|
|
||||||
message_box.append(message_text)
|
message_box.append(message_text)
|
||||||
overlay = Gtk.Overlay(css_classes=["message"], name=id)
|
overlay = Gtk.Overlay(css_classes=["message"], name=message_id)
|
||||||
overlay.set_child(message_box)
|
overlay.set_child(message_box)
|
||||||
|
|
||||||
delete_button.connect("clicked", lambda button, element=overlay: self.delete_message(element))
|
delete_button.connect("clicked", lambda button, element=overlay: self.delete_message(element))
|
||||||
copy_button.connect("clicked", lambda button, element=overlay: self.copy_message(element))
|
copy_button.connect("clicked", lambda button, element=overlay: self.copy_message(element))
|
||||||
edit_button.connect("clicked", lambda button, element=overlay, textview=message_text, button_container=button_container: self.edit_message(element, textview, button_container))
|
edit_button.connect("clicked", lambda button, element=overlay, textview=message_text, button_container=button_container: self.edit_message(element, textview, button_container))
|
||||||
|
regenerate_button.connect('clicked', lambda button, message_id=message_id, bot_message_box=message_box, bot_message_button_container=button_container : self.regenerate_message(message_id, bot_message_box, bot_message_button_container))
|
||||||
button_container.append(delete_button)
|
button_container.append(delete_button)
|
||||||
button_container.append(copy_button)
|
button_container.append(copy_button)
|
||||||
if not bot: button_container.append(edit_button)
|
button_container.append(regenerate_button if bot else edit_button)
|
||||||
overlay.add_overlay(button_container)
|
overlay.add_overlay(button_container)
|
||||||
self.chat_container.append(overlay)
|
self.chat_container.append(overlay)
|
||||||
|
|
||||||
@@ -756,32 +794,31 @@ Generate a title following these rules:
|
|||||||
else:
|
else:
|
||||||
self.local_model_list_box.set_visible(True)
|
self.local_model_list_box.set_visible(True)
|
||||||
for model in json.loads(response.text)['models']:
|
for model in json.loads(response.text)['models']:
|
||||||
|
model_name = self.convert_model_name(model["name"], 0)
|
||||||
model_row = Adw.ActionRow(
|
model_row = Adw.ActionRow(
|
||||||
title = "<b>{}</b>".format(model["name"].split(":")[0].replace("-", " ").title()),
|
title = "<b>{}</b>".format(model_name.split(" (")[0]),
|
||||||
subtitle = model["name"].split(":")[1]
|
subtitle = model_name.split(" (")[1][:-1]
|
||||||
)
|
)
|
||||||
button = Gtk.Button(
|
button = Gtk.Button(
|
||||||
icon_name = "user-trash-symbolic",
|
icon_name = "user-trash-symbolic",
|
||||||
vexpand = False,
|
vexpand = False,
|
||||||
valign = 3,
|
valign = 3,
|
||||||
css_classes = ["error", "circular"],
|
css_classes = ["error", "circular"],
|
||||||
tooltip_text = _("Remove '{} ({})'").format(model["name"].split(":")[0].replace('-', ' ').title(), model["name"].split(":")[1])
|
tooltip_text = _("Remove '{}'").format(model_name)
|
||||||
)
|
)
|
||||||
button.connect("clicked", lambda button=button, model_name=model["name"]: dialogs.delete_model(self, model_name))
|
button.connect("clicked", lambda button=button, model_name=model["name"]: dialogs.delete_model(self, model_name))
|
||||||
model_row.add_suffix(button)
|
model_row.add_suffix(button)
|
||||||
self.local_model_list_box.append(model_row)
|
self.local_model_list_box.append(model_row)
|
||||||
|
|
||||||
self.model_string_list.append(f"{model['name'].split(':')[0].replace('-', ' ').title()} ({model['name'].split(':')[1]})")
|
self.model_string_list.append(model_name)
|
||||||
self.local_models.append(model["name"])
|
self.local_models.append(model["name"])
|
||||||
self.model_drop_down.set_selected(0)
|
#self.verify_if_image_can_be_used()
|
||||||
self.verify_if_image_can_be_used()
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
|
|
||||||
def save_server_config(self):
|
def save_server_config(self):
|
||||||
with open(os.path.join(self.config_dir, "server.json"), "w+") as f:
|
with open(os.path.join(self.config_dir, "server.json"), "w+", encoding="utf-8") as f:
|
||||||
json.dump({'remote_url': self.remote_url, 'remote_bearer_token': self.remote_bearer_token, 'run_remote': self.run_remote, 'local_port': local_instance.port, 'run_on_background': self.run_on_background, 'model_tweaks': self.model_tweaks, 'ollama_overrides': local_instance.overrides, 'show_support': self.show_support}, f, indent=6)
|
json.dump({'remote_url': self.remote_url, 'remote_bearer_token': self.remote_bearer_token, 'run_remote': self.run_remote, 'local_port': local_instance.port, 'run_on_background': self.run_on_background, 'model_tweaks': self.model_tweaks, 'ollama_overrides': local_instance.overrides}, f, indent=6)
|
||||||
|
|
||||||
def verify_connection(self):
|
def verify_connection(self):
|
||||||
try:
|
try:
|
||||||
@@ -937,20 +974,22 @@ Generate a title following these rules:
|
|||||||
def generate_datetime_format(self, dt:datetime) -> str:
|
def generate_datetime_format(self, dt:datetime) -> str:
|
||||||
date = GLib.DateTime.new(GLib.DateTime.new_now_local().get_timezone(), dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
|
date = GLib.DateTime.new(GLib.DateTime.new_now_local().get_timezone(), dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
|
||||||
current_date = GLib.DateTime.new_now_local()
|
current_date = GLib.DateTime.new_now_local()
|
||||||
if date.format("%Y/%m/%d") == current_date.format("%Y/%m/%d"): return date.format("%H:%M %p")
|
if date.format("%Y/%m/%d") == current_date.format("%Y/%m/%d"):
|
||||||
elif date.format("%Y") == current_date.format("%Y"): return date.format("%b %d, %H:%M %p")
|
return date.format("%H:%M %p")
|
||||||
else: return date.format("%b %d %Y, %H:%M %p")
|
if date.format("%Y") == current_date.format("%Y"):
|
||||||
|
return date.format("%b %d, %H:%M %p")
|
||||||
|
return date.format("%b %d %Y, %H:%M %p")
|
||||||
|
|
||||||
def update_bot_message(self, data, id):
|
def update_bot_message(self, data, message_id):
|
||||||
if self.bot_message is None:
|
if self.bot_message is None:
|
||||||
self.save_history()
|
self.save_history()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
vadjustment = self.chat_window.get_vadjustment()
|
vadjustment = self.chat_window.get_vadjustment()
|
||||||
if id not in self.chats["chats"][self.chats["selected_chat"]]["messages"] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
|
if message_id not in self.chats["chats"][self.chats["selected_chat"]]["messages"] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
|
||||||
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
|
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
|
||||||
if data['done']:
|
if 'done' in data and data['done']:
|
||||||
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["date"], '%Y/%m/%d %H:%M:%S')))
|
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["date"], '%Y/%m/%d %H:%M:%S')))
|
||||||
text = f"\n\n{data['model'].split(':')[0].replace('-', ' ').title()} ({data['model'].split(':')[1]})\n<small>{formated_date}</small>"
|
text = f"\n\n{self.convert_model_name(data['model'], 0)}\n<small>{formated_date}</small>"
|
||||||
GLib.idle_add(self.bot_message.insert_markup, self.bot_message.get_end_iter(), text, len(text.encode('utf-8')))
|
GLib.idle_add(self.bot_message.insert_markup, self.bot_message.get_end_iter(), text, len(text.encode('utf-8')))
|
||||||
self.save_history()
|
self.save_history()
|
||||||
GLib.idle_add(self.bot_message_button_container.set_visible, True)
|
GLib.idle_add(self.bot_message_button_container.set_visible, True)
|
||||||
@@ -958,17 +997,11 @@ Generate a title following these rules:
|
|||||||
first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0]
|
first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0]
|
||||||
GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic"))
|
GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic"))
|
||||||
else:
|
else:
|
||||||
if id not in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
if not self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["content"] and self.loading_spinner:
|
||||||
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
||||||
self.loading_spinner = None
|
self.loading_spinner = None
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id] = {
|
|
||||||
"role": "assistant",
|
|
||||||
"model": data['model'],
|
|
||||||
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
|
|
||||||
"content": ''
|
|
||||||
}
|
|
||||||
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
|
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['content'] += data['message']['content']
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] += data['message']['content']
|
||||||
|
|
||||||
def toggle_ui_sensitive(self, status):
|
def toggle_ui_sensitive(self, status):
|
||||||
for element in [self.chat_list_box, self.add_chat_button, self.secondary_menu_button]:
|
for element in [self.chat_list_box, self.add_chat_button, self.secondary_menu_button]:
|
||||||
@@ -978,21 +1011,75 @@ Generate a title following these rules:
|
|||||||
self.stop_button.set_visible(self.send_button.get_visible())
|
self.stop_button.set_visible(self.send_button.get_visible())
|
||||||
self.send_button.set_visible(not self.send_button.get_visible())
|
self.send_button.set_visible(not self.send_button.get_visible())
|
||||||
|
|
||||||
def run_message(self, messages, model, id):
|
def run_message(self, messages, model, message_id):
|
||||||
logger.debug("Running message")
|
logger.debug("Running message")
|
||||||
self.bot_message_button_container.set_visible(False)
|
self.bot_message_button_container.set_visible(False)
|
||||||
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, id=id: self.update_bot_message(data, id))
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
|
||||||
|
"role": "assistant",
|
||||||
|
"model": model,
|
||||||
|
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
|
||||||
|
"content": ''
|
||||||
|
}
|
||||||
|
if self.regenerate_button:
|
||||||
|
GLib.idle_add(self.chat_container.remove, self.regenerate_button)
|
||||||
|
try:
|
||||||
|
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, message_id=message_id: self.update_bot_message(data, message_id))
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception('Network Error')
|
||||||
GLib.idle_add(self.add_code_blocks)
|
GLib.idle_add(self.add_code_blocks)
|
||||||
|
except Exception as e:
|
||||||
|
GLib.idle_add(self.connection_error)
|
||||||
|
self.regenerate_button = Gtk.Button(
|
||||||
|
child=Adw.ButtonContent(
|
||||||
|
icon_name='update-symbolic',
|
||||||
|
label=_('Regenerate Response')
|
||||||
|
),
|
||||||
|
css_classes=["suggested-action"],
|
||||||
|
halign=3
|
||||||
|
)
|
||||||
|
GLib.idle_add(self.chat_container.append, self.regenerate_button)
|
||||||
|
self.regenerate_button.connect('clicked', lambda button, message_id=message_id, bot_message_box=self.bot_message_box, bot_message_button_container=self.bot_message_button_container : self.regenerate_message(message_id, bot_message_box, bot_message_button_container))
|
||||||
|
finally:
|
||||||
GLib.idle_add(self.switch_send_stop_button)
|
GLib.idle_add(self.switch_send_stop_button)
|
||||||
GLib.idle_add(self.toggle_ui_sensitive, True)
|
GLib.idle_add(self.toggle_ui_sensitive, True)
|
||||||
if self.loading_spinner:
|
if self.loading_spinner:
|
||||||
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
||||||
self.loading_spinner = None
|
self.loading_spinner = None
|
||||||
if response.status_code != 200:
|
|
||||||
GLib.idle_add(self.connection_error)
|
def regenerate_message(self, message_id, bot_message_box, bot_message_button_container):
|
||||||
|
self.bot_message_button_container = bot_message_button_container
|
||||||
|
self.bot_message_view = Gtk.TextView(
|
||||||
|
editable=False,
|
||||||
|
focusable=True,
|
||||||
|
wrap_mode= Gtk.WrapMode.WORD,
|
||||||
|
margin_top=12,
|
||||||
|
margin_bottom=12,
|
||||||
|
hexpand=True,
|
||||||
|
css_classes=["flat"]
|
||||||
|
)
|
||||||
|
self.bot_message = self.bot_message_view.get_buffer()
|
||||||
|
for widget in list(bot_message_box):
|
||||||
|
bot_message_box.remove(widget)
|
||||||
|
bot_message_box.append(self.bot_message_view)
|
||||||
|
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
|
||||||
|
if message_id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
||||||
|
del self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]
|
||||||
|
data = {
|
||||||
|
"model": self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1),
|
||||||
|
"messages": history,
|
||||||
|
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
||||||
|
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
|
||||||
|
}
|
||||||
|
self.switch_send_stop_button()
|
||||||
|
self.toggle_ui_sensitive(False)
|
||||||
|
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], message_id))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def pull_model_update(self, data, model_name):
|
def pull_model_update(self, data, model_name):
|
||||||
if model_name in list(self.pulling_models.keys()):
|
if 'error' in data:
|
||||||
|
self.pulling_models[model_name]['error'] = data['error']
|
||||||
|
return
|
||||||
|
if model_name in self.pulling_models.keys():
|
||||||
if 'completed' in data and 'total' in data:
|
if 'completed' in data and 'total' in data:
|
||||||
GLib.idle_add(self.pulling_models[model_name]['row'].set_subtitle, '<tt>{}%</tt>'.format(round(data['completed'] / data['total'] * 100, 2)))
|
GLib.idle_add(self.pulling_models[model_name]['row'].set_subtitle, '<tt>{}%</tt>'.format(round(data['completed'] / data['total'] * 100, 2)))
|
||||||
GLib.idle_add(self.pulling_models[model_name]['progress_bar'].set_fraction, (data['completed'] / data['total']))
|
GLib.idle_add(self.pulling_models[model_name]['progress_bar'].set_fraction, (data['completed'] / data['total']))
|
||||||
@@ -1012,28 +1099,32 @@ Generate a title following these rules:
|
|||||||
response = connection_handler.stream_post(f"{connection_handler.url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
|
response = connection_handler.stream_post(f"{connection_handler.url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
|
||||||
GLib.idle_add(self.update_list_local_models)
|
GLib.idle_add(self.update_list_local_models)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200 and 'error' not in self.pulling_models[model]:
|
||||||
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
||||||
GLib.idle_add(self.show_toast, _("Model '{}' pulled successfully.").format(model), self.manage_models_overlay)
|
GLib.idle_add(self.show_toast, _("Model '{}' pulled successfully.").format(model), self.manage_models_overlay)
|
||||||
GLib.idle_add(self.pulling_models[model]['overlay'].get_parent().get_parent().remove, self.pulling_models[model]['overlay'].get_parent())
|
elif response.status_code == 200 and self.pulling_models[model]['error']:
|
||||||
del self.pulling_models[model]
|
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}': {}").format(model, self.pulling_models[model]['error']), Gio.ThemedIcon.new("dialog-error-symbolic"))
|
||||||
|
GLib.idle_add(self.show_toast, _("Error pulling '{}': {}").format(model, self.pulling_models[model]['error']), self.manage_models_overlay)
|
||||||
else:
|
else:
|
||||||
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(model), Gio.ThemedIcon.new("dialog-error-symbolic"))
|
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(model), Gio.ThemedIcon.new("dialog-error-symbolic"))
|
||||||
GLib.idle_add(self.pulling_models[model]['overlay'].get_parent().get_parent().remove, self.pulling_models[model]['overlay'].get_parent())
|
GLib.idle_add(self.show_toast, _("Error pulling '{}'").format(model), self.manage_models_overlay)
|
||||||
del self.pulling_models[model]
|
|
||||||
GLib.idle_add(self.manage_models_dialog.close)
|
GLib.idle_add(self.manage_models_dialog.close)
|
||||||
GLib.idle_add(self.connection_error)
|
GLib.idle_add(self.connection_error)
|
||||||
|
|
||||||
|
GLib.idle_add(self.pulling_models[model]['overlay'].get_parent().get_parent().remove, self.pulling_models[model]['overlay'].get_parent())
|
||||||
|
del self.pulling_models[model]
|
||||||
if len(list(self.pulling_models.keys())) == 0:
|
if len(list(self.pulling_models.keys())) == 0:
|
||||||
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
||||||
|
|
||||||
def pull_model(self, model):
|
def pull_model(self, model):
|
||||||
logger.info("Pulling model")
|
if model in self.pulling_models.keys() or model in self.local_models or ":" not in model:
|
||||||
if model in list(self.pulling_models.keys()) or model in self.local_models:
|
|
||||||
return
|
return
|
||||||
|
logger.info("Pulling model")
|
||||||
self.pulling_model_list_box.set_visible(True)
|
self.pulling_model_list_box.set_visible(True)
|
||||||
#self.pulling_model_list_box.connect('row_selected', lambda list_box, row: dialogs.stop_pull_model(self, row.get_name()) if row else None) #It isn't working for some reason
|
#self.pulling_model_list_box.connect('row_selected', lambda list_box, row: dialogs.stop_pull_model(self, row.get_name()) if row else None) #It isn't working for some reason
|
||||||
|
model_name = self.convert_model_name(model, 0)
|
||||||
model_row = Adw.ActionRow(
|
model_row = Adw.ActionRow(
|
||||||
title = "<b>{}</b> <small>{}</small>".format(model.split(":")[0].replace("-", " ").title(), model.split(":")[1]),
|
title = "<b>{}</b> <small>{}</small>".format(model_name.split(" (")[0], model_name.split(" (")[1][:-1]),
|
||||||
name = model
|
name = model
|
||||||
)
|
)
|
||||||
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model, "modelfile": None})
|
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model, "modelfile": None})
|
||||||
@@ -1050,7 +1141,7 @@ Generate a title following these rules:
|
|||||||
vexpand = False,
|
vexpand = False,
|
||||||
valign = 3,
|
valign = 3,
|
||||||
css_classes = ["error", "circular"],
|
css_classes = ["error", "circular"],
|
||||||
tooltip_text = _("Stop Pulling '{} ({})'").format(model.split(':')[0].replace('-', ' ').title(), model.split(':')[1])
|
tooltip_text = _("Stop Pulling '{}'").format(model_name)
|
||||||
)
|
)
|
||||||
button.connect("clicked", lambda button, model_name=model : dialogs.stop_pull_model(self, model_name))
|
button.connect("clicked", lambda button, model_name=model : dialogs.stop_pull_model(self, model_name))
|
||||||
model_row.add_suffix(button)
|
model_row.add_suffix(button)
|
||||||
@@ -1107,7 +1198,7 @@ Generate a title following these rules:
|
|||||||
|
|
||||||
def save_history(self):
|
def save_history(self):
|
||||||
logger.debug("Saving history")
|
logger.debug("Saving history")
|
||||||
with open(os.path.join(self.data_dir, "chats", "chats.json"), "w+") as f:
|
with open(os.path.join(self.data_dir, "chats", "chats.json"), "w+", encoding="utf-8") as f:
|
||||||
json.dump(self.chats, f, indent=4)
|
json.dump(self.chats, f, indent=4)
|
||||||
|
|
||||||
def load_history_into_chat(self):
|
def load_history_into_chat(self):
|
||||||
@@ -1116,9 +1207,9 @@ Generate a title following these rules:
|
|||||||
if message:
|
if message:
|
||||||
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(message['date'] + (":00" if message['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S')))
|
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(message['date'] + (":00" if message['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S')))
|
||||||
if message['role'] == 'user':
|
if message['role'] == 'user':
|
||||||
self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, id=key)
|
self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, message_id=key)
|
||||||
else:
|
else:
|
||||||
self.show_message(message['content'], True, f"\n\n{message['model'].split(':')[0].replace('-', ' ').title()} ({message['model'].split(':')[1]})\n<small>{formated_date}</small>", id=key)
|
self.show_message(message['content'], True, f"\n\n{self.convert_model_name(message['model'], 0)}\n<small>{formated_date}</small>", message_id=key)
|
||||||
self.add_code_blocks()
|
self.add_code_blocks()
|
||||||
self.bot_message = None
|
self.bot_message = None
|
||||||
|
|
||||||
@@ -1126,14 +1217,23 @@ Generate a title following these rules:
|
|||||||
logger.debug("Loading history")
|
logger.debug("Loading history")
|
||||||
if os.path.exists(os.path.join(self.data_dir, "chats", "chats.json")):
|
if os.path.exists(os.path.join(self.data_dir, "chats", "chats.json")):
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(self.data_dir, "chats", "chats.json"), "r") as f:
|
with open(os.path.join(self.data_dir, "chats", "chats.json"), "r", encoding="utf-8") as f:
|
||||||
self.chats = json.load(f)
|
self.chats = json.load(f)
|
||||||
if len(list(self.chats["chats"].keys())) == 0: self.chats["chats"][_("New Chat")] = {"messages": {}}
|
if len(list(self.chats["chats"].keys())) == 0:
|
||||||
if "selected_chat" not in self.chats or self.chats["selected_chat"] not in self.chats["chats"]: self.chats["selected_chat"] = list(self.chats["chats"].keys())[0]
|
self.chats["chats"][_("New Chat")] = {"messages": {}}
|
||||||
|
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 "order" not in self.chats:
|
if "order" not in self.chats:
|
||||||
self.chats["order"] = []
|
self.chats["order"] = []
|
||||||
for chat_name in self.chats["chats"].keys():
|
for chat_name in self.chats["chats"].keys():
|
||||||
self.chats["order"].append(chat_name)
|
self.chats["order"].append(chat_name)
|
||||||
|
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
||||||
|
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
|
||||||
|
last_model_used = self.convert_model_name(last_model_used, 0)
|
||||||
|
for i in range(self.model_string_list.get_n_items()):
|
||||||
|
if self.model_string_list.get_string(i) == last_model_used:
|
||||||
|
self.model_drop_down.set_selected(i)
|
||||||
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
self.chats = {"chats": {}, "selected_chat": None, "order": []}
|
self.chats = {"chats": {}, "selected_chat": None, "order": []}
|
||||||
@@ -1182,7 +1282,8 @@ Generate a title following these rules:
|
|||||||
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
||||||
logger.info(f"Renaming chat \"{old_chat_name}\" -> \"{new_chat_name}\"")
|
logger.info(f"Renaming chat \"{old_chat_name}\" -> \"{new_chat_name}\"")
|
||||||
new_chat_name = self.generate_numbered_name(new_chat_name, self.chats["chats"].keys())
|
new_chat_name = self.generate_numbered_name(new_chat_name, self.chats["chats"].keys())
|
||||||
if self.chats["selected_chat"] == old_chat_name: self.chats["selected_chat"] = new_chat_name
|
if self.chats["selected_chat"] == old_chat_name:
|
||||||
|
self.chats["selected_chat"] = new_chat_name
|
||||||
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
|
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
|
||||||
self.chats["order"][self.chats["order"].index(old_chat_name)] = new_chat_name
|
self.chats["order"][self.chats["order"].index(old_chat_name)] = new_chat_name
|
||||||
del self.chats["chats"][old_chat_name]
|
del self.chats["chats"][old_chat_name]
|
||||||
@@ -1252,9 +1353,12 @@ Generate a title following these rules:
|
|||||||
gesture.connect("released", self.chat_click_handler)
|
gesture.connect("released", self.chat_click_handler)
|
||||||
chat_row.add_controller(gesture)
|
chat_row.add_controller(gesture)
|
||||||
|
|
||||||
if append: self.chat_list_box.append(chat_row)
|
if append:
|
||||||
else: self.chat_list_box.prepend(chat_row)
|
self.chat_list_box.append(chat_row)
|
||||||
if select: self.chat_list_box.select_row(chat_row)
|
else:
|
||||||
|
self.chat_list_box.prepend(chat_row)
|
||||||
|
if select:
|
||||||
|
self.chat_list_box.select_row(chat_row)
|
||||||
|
|
||||||
def update_chat_list(self):
|
def update_chat_list(self):
|
||||||
self.chat_list_box.remove_all()
|
self.chat_list_box.remove_all()
|
||||||
@@ -1280,8 +1384,10 @@ Generate a title following these rules:
|
|||||||
connection_handler.bearer_token = None
|
connection_handler.bearer_token = None
|
||||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
local_instance.start()
|
local_instance.start()
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False:
|
||||||
else: self.remote_connection_switch.set_active(False)
|
self.connection_error()
|
||||||
|
else:
|
||||||
|
self.remote_connection_switch.set_active(False)
|
||||||
|
|
||||||
def connection_error(self):
|
def connection_error(self):
|
||||||
logger.error("Connection error")
|
logger.error("Connection error")
|
||||||
@@ -1299,13 +1405,16 @@ Generate a title following these rules:
|
|||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
connection_handler.bearer_token = self.remote_bearer_token
|
connection_handler.bearer_token = self.remote_bearer_token
|
||||||
connection_handler.url = self.remote_url
|
connection_handler.url = self.remote_url
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False:
|
||||||
else: local_instance.stop()
|
self.connection_error()
|
||||||
|
else:
|
||||||
|
local_instance.stop()
|
||||||
else:
|
else:
|
||||||
connection_handler.bearer_token = None
|
connection_handler.bearer_token = None
|
||||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
local_instance.start()
|
local_instance.start()
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False:
|
||||||
|
self.connection_error()
|
||||||
|
|
||||||
def on_replace_contents(self, file, result):
|
def on_replace_contents(self, file, result):
|
||||||
file.replace_contents_finish(result)
|
file.replace_contents_finish(result)
|
||||||
@@ -1313,12 +1422,13 @@ Generate a title following these rules:
|
|||||||
|
|
||||||
def on_export_chat(self, file_dialog, result, chat_name):
|
def on_export_chat(self, file_dialog, result, chat_name):
|
||||||
file = file_dialog.save_finish(result)
|
file = file_dialog.save_finish(result)
|
||||||
if not file: return
|
if not file:
|
||||||
|
return
|
||||||
json_data = json.dumps({chat_name: self.chats["chats"][chat_name]}, indent=4).encode("UTF-8")
|
json_data = json.dumps({chat_name: self.chats["chats"][chat_name]}, indent=4).encode("UTF-8")
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
json_path = os.path.join(temp_dir, "data.json")
|
json_path = os.path.join(temp_dir, "data.json")
|
||||||
with open(json_path, "wb") as json_file:
|
with open(json_path, "wb", encoding="utf-8") as json_file:
|
||||||
json_file.write(json_data)
|
json_file.write(json_data)
|
||||||
|
|
||||||
tar_path = os.path.join(temp_dir, chat_name)
|
tar_path = os.path.join(temp_dir, chat_name)
|
||||||
@@ -1328,7 +1438,7 @@ Generate a title following these rules:
|
|||||||
if os.path.exists(directory) and os.path.isdir(directory):
|
if os.path.exists(directory) and os.path.isdir(directory):
|
||||||
tar.add(directory, arcname=os.path.basename(directory))
|
tar.add(directory, arcname=os.path.basename(directory))
|
||||||
|
|
||||||
with open(tar_path, "rb") as tar:
|
with open(tar_path, "rb", encoding="utf-8") as tar:
|
||||||
tar_content = tar.read()
|
tar_content = tar.read()
|
||||||
|
|
||||||
file.replace_contents_async(
|
file.replace_contents_async(
|
||||||
@@ -1347,7 +1457,8 @@ Generate a title following these rules:
|
|||||||
|
|
||||||
def on_chat_imported(self, file_dialog, result):
|
def on_chat_imported(self, file_dialog, result):
|
||||||
file = file_dialog.open_finish(result)
|
file = file_dialog.open_finish(result)
|
||||||
if not file: return
|
if not file:
|
||||||
|
return
|
||||||
stream = file.read(None)
|
stream = file.read(None)
|
||||||
data_stream = Gio.DataInputStream.new(stream)
|
data_stream = Gio.DataInputStream.new(stream)
|
||||||
tar_content = data_stream.read_bytes(1024 * 1024, None)
|
tar_content = data_stream.read_bytes(1024 * 1024, None)
|
||||||
@@ -1355,7 +1466,7 @@ Generate a title following these rules:
|
|||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
tar_filename = os.path.join(temp_dir, "imported_chat.tar")
|
tar_filename = os.path.join(temp_dir, "imported_chat.tar")
|
||||||
|
|
||||||
with open(tar_filename, "wb") as tar_file:
|
with open(tar_filename, "wb", encoding="utf-8") as tar_file:
|
||||||
tar_file.write(tar_content.get_data())
|
tar_file.write(tar_content.get_data())
|
||||||
|
|
||||||
with tarfile.open(tar_filename, "r") as tar:
|
with tarfile.open(tar_filename, "r") as tar:
|
||||||
@@ -1365,7 +1476,7 @@ Generate a title following these rules:
|
|||||||
for member in tar.getmembers():
|
for member in tar.getmembers():
|
||||||
if member.name == "data.json":
|
if member.name == "data.json":
|
||||||
json_filepath = os.path.join(temp_dir, member.name)
|
json_filepath = os.path.join(temp_dir, member.name)
|
||||||
with open(json_filepath, "r") as json_file:
|
with open(json_filepath, "r", encoding="utf-8") as json_file:
|
||||||
data = json.load(json_file)
|
data = json.load(json_file)
|
||||||
for chat_name, chat_content in data.items():
|
for chat_name, chat_content in data.items():
|
||||||
new_chat_name = self.generate_numbered_name(chat_name, list(self.chats['chats'].keys()))
|
new_chat_name = self.generate_numbered_name(chat_name, list(self.chats['chats'].keys()))
|
||||||
@@ -1413,11 +1524,12 @@ Generate a title following these rules:
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
self.show_toast(_("Cannot open image"), self.main_overlay)
|
self.show_toast(_("Cannot open image"), self.main_overlay)
|
||||||
elif file_type == 'plain_text' or file_type == 'youtube' or file_type == 'website':
|
elif file_type == 'plain_text' or file_type == 'youtube' or file_type == 'website':
|
||||||
with open(file_path, 'r') as f:
|
with open(file_path, 'r', encoding="utf-8") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
elif file_type == 'pdf':
|
elif file_type == 'pdf':
|
||||||
reader = PdfReader(file_path)
|
reader = PdfReader(file_path)
|
||||||
if len(reader.pages) == 0: return None
|
if len(reader.pages) == 0:
|
||||||
|
return None
|
||||||
text = ""
|
text = ""
|
||||||
for i, page in enumerate(reader.pages):
|
for i, page in enumerate(reader.pages):
|
||||||
text += f"\n- Page {i}\n{page.extract_text(extraction_mode='layout', layout_mode_space_vertically=False)}\n"
|
text += f"\n- Page {i}\n{page.extract_text(extraction_mode='layout', layout_mode_space_vertically=False)}\n"
|
||||||
@@ -1428,7 +1540,8 @@ Generate a title following these rules:
|
|||||||
button = self.attachments[name]['button']
|
button = self.attachments[name]['button']
|
||||||
button.get_parent().remove(button)
|
button.get_parent().remove(button)
|
||||||
del self.attachments[name]
|
del self.attachments[name]
|
||||||
if len(self.attachments) == 0: self.attachment_box.set_visible(False)
|
if len(self.attachments) == 0:
|
||||||
|
self.attachment_box.set_visible(False)
|
||||||
|
|
||||||
def attach_file(self, file_path, file_type):
|
def attach_file(self, file_path, file_type):
|
||||||
logger.debug(f"Attaching file: {file_path}")
|
logger.debug(f"Attaching file: {file_path}")
|
||||||
@@ -1539,26 +1652,35 @@ Generate a title following these rules:
|
|||||||
factory.connect("bind", self.on_model_dropdown_bind)
|
factory.connect("bind", self.on_model_dropdown_bind)
|
||||||
self.model_drop_down.set_factory(factory)
|
self.model_drop_down.set_factory(factory)
|
||||||
|
|
||||||
|
def handle_enter_key(self):
|
||||||
|
self.send_message()
|
||||||
|
return True
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
GtkSource.init()
|
GtkSource.init()
|
||||||
with open('/app/share/Alpaca/alpaca/available_models.json', 'r') as f:
|
with open(os.path.join(source_dir, 'available_models.json'), 'r', encoding="utf-8") as f:
|
||||||
self.available_models = json.load(f)
|
self.available_models = json.load(f)
|
||||||
if not os.path.exists(os.path.join(self.data_dir, "chats")):
|
if not os.path.exists(os.path.join(self.data_dir, "chats")):
|
||||||
os.makedirs(os.path.join(self.data_dir, "chats"))
|
os.makedirs(os.path.join(self.data_dir, "chats"))
|
||||||
|
key_controller = Gtk.EventControllerKey.new()
|
||||||
|
key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return else None)
|
||||||
|
self.message_text_view.add_controller(key_controller)
|
||||||
self.set_help_overlay(self.shortcut_window)
|
self.set_help_overlay(self.shortcut_window)
|
||||||
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
|
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
|
||||||
self.get_application().create_action('new_chat', lambda *_: self.new_chat(), ['<primary>n'])
|
self.get_application().create_action('new_chat', lambda *_: self.new_chat(), ['<primary>n'])
|
||||||
self.get_application().create_action('clear', lambda *_: dialogs.clear_chat(self), ['<primary>e'])
|
self.get_application().create_action('clear', lambda *_: dialogs.clear_chat(self), ['<primary>e'])
|
||||||
self.get_application().create_action('send', lambda *_: self.send_message(self), ['Return'])
|
|
||||||
self.get_application().create_action('import_chat', lambda *_: self.import_chat(), ['<primary>i'])
|
self.get_application().create_action('import_chat', lambda *_: self.import_chat(), ['<primary>i'])
|
||||||
self.get_application().create_action('create_model_from_existing', lambda *_: dialogs.create_model_from_existing(self))
|
self.get_application().create_action('create_model_from_existing', lambda *_: dialogs.create_model_from_existing(self))
|
||||||
self.get_application().create_action('create_model_from_file', lambda *_: dialogs.create_model_from_file(self))
|
self.get_application().create_action('create_model_from_file', lambda *_: dialogs.create_model_from_file(self))
|
||||||
|
self.get_application().create_action('create_model_from_name', lambda *_: dialogs.create_model_from_name(self))
|
||||||
self.get_application().create_action('delete_chat', self.chat_actions)
|
self.get_application().create_action('delete_chat', self.chat_actions)
|
||||||
self.get_application().create_action('rename_chat', self.chat_actions)
|
self.get_application().create_action('rename_chat', self.chat_actions)
|
||||||
self.get_application().create_action('rename_current_chat', self.current_chat_actions)
|
self.get_application().create_action('rename_current_chat', self.current_chat_actions)
|
||||||
self.get_application().create_action('export_chat', self.chat_actions)
|
self.get_application().create_action('export_chat', self.chat_actions)
|
||||||
self.get_application().create_action('export_current_chat', self.current_chat_actions)
|
self.get_application().create_action('export_current_chat', self.current_chat_actions)
|
||||||
|
self.get_application().create_action('toggle_sidebar', lambda *_: self.split_view_overlay.set_show_sidebar(not self.split_view_overlay.get_show_sidebar()), ['F9'])
|
||||||
|
self.get_application().create_action('manage_models', lambda *_: self.manage_models_button_activate(), ['<primary>m'])
|
||||||
self.message_text_view.connect("paste-clipboard", self.on_clipboard_paste)
|
self.message_text_view.connect("paste-clipboard", self.on_clipboard_paste)
|
||||||
self.file_preview_remove_button.connect('clicked', lambda button : dialogs.remove_attached_file(self, button.get_name()))
|
self.file_preview_remove_button.connect('clicked', lambda button : dialogs.remove_attached_file(self, button.get_name()))
|
||||||
self.add_chat_button.connect("clicked", lambda button : self.new_chat())
|
self.add_chat_button.connect("clicked", lambda button : self.new_chat())
|
||||||
@@ -1569,7 +1691,7 @@ Generate a title following these rules:
|
|||||||
self.background_switch.connect("notify", lambda pspec, user_data : self.switch_run_on_background())
|
self.background_switch.connect("notify", lambda pspec, user_data : self.switch_run_on_background())
|
||||||
self.setup_model_dropdown()
|
self.setup_model_dropdown()
|
||||||
if os.path.exists(os.path.join(self.config_dir, "server.json")):
|
if os.path.exists(os.path.join(self.config_dir, "server.json")):
|
||||||
with open(os.path.join(self.config_dir, "server.json"), "r") as f:
|
with open(os.path.join(self.config_dir, "server.json"), "r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
self.run_remote = data['run_remote']
|
self.run_remote = data['run_remote']
|
||||||
local_instance.port = data['local_port']
|
local_instance.port = data['local_port']
|
||||||
@@ -1582,7 +1704,8 @@ Generate a title following these rules:
|
|||||||
self.seed_spin.set_value(self.model_tweaks['seed'])
|
self.seed_spin.set_value(self.model_tweaks['seed'])
|
||||||
self.keep_alive_spin.set_value(self.model_tweaks['keep_alive'])
|
self.keep_alive_spin.set_value(self.model_tweaks['keep_alive'])
|
||||||
#Overrides
|
#Overrides
|
||||||
if "ollama_overrides" in data: local_instance.overrides = data['ollama_overrides']
|
if "ollama_overrides" in data:
|
||||||
|
local_instance.overrides = data['ollama_overrides']
|
||||||
for element in [
|
for element in [
|
||||||
self.override_HSA_OVERRIDE_GFX_VERSION,
|
self.override_HSA_OVERRIDE_GFX_VERSION,
|
||||||
self.override_CUDA_VISIBLE_DEVICES,
|
self.override_CUDA_VISIBLE_DEVICES,
|
||||||
@@ -1591,11 +1714,6 @@ Generate a title following these rules:
|
|||||||
if override in local_instance.overrides:
|
if override in local_instance.overrides:
|
||||||
element.set_text(local_instance.overrides[override])
|
element.set_text(local_instance.overrides[override])
|
||||||
|
|
||||||
#Support dialog
|
|
||||||
if 'show_support' not in data or data['show_support']:
|
|
||||||
if random.randint(0, 49) == 0:
|
|
||||||
dialogs.support(self)
|
|
||||||
if 'show_support' in data: self.show_support = data['show_support']
|
|
||||||
self.background_switch.set_active(self.run_on_background)
|
self.background_switch.set_active(self.run_on_background)
|
||||||
self.set_hide_on_close(self.run_on_background)
|
self.set_hide_on_close(self.run_on_background)
|
||||||
self.remote_connection_entry.set_text(self.remote_url)
|
self.remote_connection_entry.set_text(self.remote_url)
|
||||||
@@ -1613,7 +1731,8 @@ Generate a title following these rules:
|
|||||||
local_instance.start()
|
local_instance.start()
|
||||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
self.welcome_dialog.present(self)
|
self.welcome_dialog.present(self)
|
||||||
if self.verify_connection() is False: self.connection_error()
|
if self.verify_connection() is False:
|
||||||
|
self.connection_error()
|
||||||
self.update_list_available_models()
|
self.update_list_available_models()
|
||||||
self.load_history()
|
self.load_history()
|
||||||
self.update_chat_list()
|
self.update_chat_list()
|
||||||
|
|||||||
313
src/window.ui
313
src/window.ui
@@ -5,7 +5,7 @@
|
|||||||
<template class="AlpacaWindow" parent="AdwApplicationWindow">
|
<template class="AlpacaWindow" parent="AdwApplicationWindow">
|
||||||
<signal name="close-request" handler="closing_app"/>
|
<signal name="close-request" handler="closing_app"/>
|
||||||
<property name="resizable">True</property>
|
<property name="resizable">True</property>
|
||||||
<property name="width-request">360</property>
|
<property name="width-request">400</property>
|
||||||
<property name="height-request">400</property>
|
<property name="height-request">400</property>
|
||||||
<property name="default-width">1300</property>
|
<property name="default-width">1300</property>
|
||||||
<property name="default-height">800</property>
|
<property name="default-height">800</property>
|
||||||
@@ -14,21 +14,6 @@
|
|||||||
<object class="AdwBreakpoint">
|
<object class="AdwBreakpoint">
|
||||||
<condition>max-width: 800sp</condition>
|
<condition>max-width: 800sp</condition>
|
||||||
<setter object="split_view_overlay" property="collapsed">true</setter>
|
<setter object="split_view_overlay" property="collapsed">true</setter>
|
||||||
<setter object="show_sidebar_button" property="visible">true</setter>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="AdwBreakpoint">
|
|
||||||
<condition>max-width: 500sp</condition>
|
|
||||||
<setter object="split_view_overlay" property="collapsed">true</setter>
|
|
||||||
<setter object="show_sidebar_button" property="visible">true</setter>
|
|
||||||
<setter object="welcome_dialog" property="width-request">360</setter>
|
|
||||||
<setter object="manage_models_dialog" property="width-request">360</setter>
|
|
||||||
<setter object="create_model_dialog" property="width-request">360</setter>
|
|
||||||
<setter object="preferences_dialog" property="width-request">360</setter>
|
|
||||||
<setter object="file_preview_dialog" property="width-request">360</setter>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<property name="content">
|
<property name="content">
|
||||||
@@ -80,7 +65,6 @@
|
|||||||
<object class="AdwHeaderBar" id="header_bar">
|
<object class="AdwHeaderBar" id="header_bar">
|
||||||
<child type="start">
|
<child type="start">
|
||||||
<object class="GtkToggleButton" id="show_sidebar_button">
|
<object class="GtkToggleButton" id="show_sidebar_button">
|
||||||
<property name="visible">false</property>
|
|
||||||
<property name="icon-name">sidebar-show-symbolic</property>
|
<property name="icon-name">sidebar-show-symbolic</property>
|
||||||
<property name="tooltip-text" translatable="yes">Toggle Sidebar</property>
|
<property name="tooltip-text" translatable="yes">Toggle Sidebar</property>
|
||||||
<property name="active" bind-source="split_view_overlay" bind-property="show-sidebar" bind-flags="sync-create"/>
|
<property name="active" bind-source="split_view_overlay" bind-property="show-sidebar" bind-flags="sync-create"/>
|
||||||
@@ -93,7 +77,7 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="GtkDropDown" id="model_drop_down">
|
<object class="GtkDropDown" id="model_drop_down">
|
||||||
<signal name="notify" handler="verify_if_image_can_be_used"/>
|
<signal name="notify" handler="verify_if_image_can_be_used"/>
|
||||||
<property name="width-request">150</property>
|
<property name="width-request">260</property>
|
||||||
<property name="enable-search">true</property>
|
<property name="enable-search">true</property>
|
||||||
<property name="tooltip-text">Select Model</property>
|
<property name="tooltip-text">Select Model</property>
|
||||||
<property name="model">
|
<property name="model">
|
||||||
@@ -104,7 +88,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<!--<child>
|
||||||
<object class="GtkButton" id="manage_models_button">
|
<object class="GtkButton" id="manage_models_button">
|
||||||
<signal name="clicked" handler="manage_models_button_activate"/>
|
<signal name="clicked" handler="manage_models_button_activate"/>
|
||||||
<property name="tooltip-text" translatable="yes">Manage Models</property>
|
<property name="tooltip-text" translatable="yes">Manage Models</property>
|
||||||
@@ -114,7 +98,7 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>-->
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
<child type="end">
|
<child type="end">
|
||||||
@@ -217,6 +201,8 @@
|
|||||||
<object class="GtkScrolledWindow">
|
<object class="GtkScrolledWindow">
|
||||||
<property name="max-content-height">150</property>
|
<property name="max-content-height">150</property>
|
||||||
<property name="propagate-natural-height">true</property>
|
<property name="propagate-natural-height">true</property>
|
||||||
|
<property name="margin-start">10</property>
|
||||||
|
<property name="margin-end">10</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="message_input_scroll_window"/>
|
<class name="message_input_scroll_window"/>
|
||||||
</style>
|
</style>
|
||||||
@@ -226,8 +212,6 @@
|
|||||||
<class name="message_text_view"/>
|
<class name="message_text_view"/>
|
||||||
</style>
|
</style>
|
||||||
<property name="wrap-mode">word</property>
|
<property name="wrap-mode">word</property>
|
||||||
<property name="margin-start">10</property>
|
|
||||||
<property name="margin-end">10</property>
|
|
||||||
<property name="top-margin">10</property>
|
<property name="top-margin">10</property>
|
||||||
<property name="bottom-margin">10</property>
|
<property name="bottom-margin">10</property>
|
||||||
<property name="hexpand">true</property>
|
<property name="hexpand">true</property>
|
||||||
@@ -246,6 +230,7 @@
|
|||||||
<style>
|
<style>
|
||||||
<class name="accent"/>
|
<class name="accent"/>
|
||||||
<class name="circular"/>
|
<class name="circular"/>
|
||||||
|
<class name="suggested-action"/>
|
||||||
</style>
|
</style>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwButtonContent">
|
<object class="AdwButtonContent">
|
||||||
@@ -456,127 +441,6 @@
|
|||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
<object class="AdwDialog" id="create_model_dialog">
|
|
||||||
<property name="can-close">true</property>
|
|
||||||
<property name="width-request">400</property>
|
|
||||||
<property name="height-request">600</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwToastOverlay" id="create_model_overlay">
|
|
||||||
<child>
|
|
||||||
<object class="AdwToolbarView">
|
|
||||||
<child type="bottom">
|
|
||||||
<object class="GtkActionBar">
|
|
||||||
<property name="revealed">true</property>
|
|
||||||
<child type="end">
|
|
||||||
<object class="GtkButton">
|
|
||||||
<property name="label" translatable="yes">Create</property>
|
|
||||||
<signal name="clicked" handler="create_model_start"/>
|
|
||||||
<style>
|
|
||||||
<class name="suggested-action"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child type="top">
|
|
||||||
<object class="AdwHeaderBar">
|
|
||||||
<property name="title-widget">
|
|
||||||
<object class="AdwWindowTitle">
|
|
||||||
<property name="title" translatable="yes">Create Model</property>
|
|
||||||
</object>
|
|
||||||
</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkScrolledWindow">
|
|
||||||
<property name="vexpand">true</property>
|
|
||||||
<property name="hexpand">true</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="margin-start">12</property>
|
|
||||||
<property name="margin-end">12</property>
|
|
||||||
<property name="margin-top">12</property>
|
|
||||||
<property name="margin-bottom">12</property>
|
|
||||||
<property name="orientation">1</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkListBox">
|
|
||||||
<style>
|
|
||||||
<class name="boxed-list"/>
|
|
||||||
<class name="card"/>
|
|
||||||
</style>
|
|
||||||
<property name="selection-mode">none</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwActionRow" id="create_model_base">
|
|
||||||
<property name="title" translatable="yes">Base</property>
|
|
||||||
<property name="subtitle"/>
|
|
||||||
<style>
|
|
||||||
<class name="property"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkListBox">
|
|
||||||
<style>
|
|
||||||
<class name="boxed-list"/>
|
|
||||||
<class name="card"/>
|
|
||||||
</style>
|
|
||||||
<property name="selection-mode">none</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwEntryRow" id="create_model_name">
|
|
||||||
<property name="title" translatable="yes">Name</property>
|
|
||||||
<property name="input-purpose">alpha</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="AdwEntryRow" id="create_model_system">
|
|
||||||
<property name="title" translatable="yes">Context</property>
|
|
||||||
<property name="input-purpose">alpha</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkListBox">
|
|
||||||
<style>
|
|
||||||
<class name="boxed-list"/>
|
|
||||||
<class name="card"/>
|
|
||||||
</style>
|
|
||||||
<property name="selection-mode">none</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwEntryRow" id="create_model_template">
|
|
||||||
<property name="title" translatable="yes">Template</property>
|
|
||||||
<property name="input-purpose">alpha</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="label" translatable="yes">Some models require a specific template. Please visit the model's website for more information if you're unsure.</property>
|
|
||||||
<property name="margin-start">12</property>
|
|
||||||
<property name="margin-end">12</property>
|
|
||||||
<property name="margin-top">12</property>
|
|
||||||
<property name="margin-bottom">12</property>
|
|
||||||
<property name="halign">1</property>
|
|
||||||
<property name="wrap">true</property>
|
|
||||||
<style>
|
|
||||||
<class name="caption"/>
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
|
|
||||||
<object class="AdwDialog" id="manage_models_dialog">
|
<object class="AdwDialog" id="manage_models_dialog">
|
||||||
<property name="can-close">true</property>
|
<property name="can-close">true</property>
|
||||||
<property name="width-request">400</property>
|
<property name="width-request">400</property>
|
||||||
@@ -730,6 +594,143 @@
|
|||||||
</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwNavigationPage">
|
||||||
|
<property name="title" translatable="yes">Create Model</property>
|
||||||
|
<property name="tag">model_create_page</property>
|
||||||
|
<property name="child">
|
||||||
|
<object class="AdwToolbarView">
|
||||||
|
<child type="top">
|
||||||
|
<object class="AdwHeaderBar">
|
||||||
|
<child type="start">
|
||||||
|
<object class="GtkButton">
|
||||||
|
<signal name="clicked" handler="link_button_handler"/>
|
||||||
|
<property name="icon-name">globe-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<property name="content">
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="vexpand">true</property>
|
||||||
|
<property name="hexpand">true</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="margin-start">12</property>
|
||||||
|
<property name="margin-end">12</property>
|
||||||
|
<property name="margin-top">12</property>
|
||||||
|
<property name="margin-bottom">12</property>
|
||||||
|
<property name="orientation">1</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox">
|
||||||
|
<style>
|
||||||
|
<class name="boxed-list"/>
|
||||||
|
<class name="card"/>
|
||||||
|
</style>
|
||||||
|
<property name="selection-mode">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="AdwActionRow" id="create_model_base">
|
||||||
|
<property name="title" translatable="yes">Base</property>
|
||||||
|
<property name="sensitive">false</property>
|
||||||
|
<property name="subtitle"/>
|
||||||
|
<style>
|
||||||
|
<class name="property"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox">
|
||||||
|
<style>
|
||||||
|
<class name="boxed-list"/>
|
||||||
|
<class name="card"/>
|
||||||
|
</style>
|
||||||
|
<property name="selection-mode">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="AdwEntryRow" id="create_model_name">
|
||||||
|
<property name="title" translatable="yes">Name</property>
|
||||||
|
<property name="input-purpose">alpha</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwEntryRow" id="create_model_system">
|
||||||
|
<property name="title" translatable="yes">Context</property>
|
||||||
|
<property name="input-purpose">alpha</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox">
|
||||||
|
<style>
|
||||||
|
<class name="boxed-list"/>
|
||||||
|
<class name="card"/>
|
||||||
|
</style>
|
||||||
|
<property name="selection-mode">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="height-request">140</property>
|
||||||
|
<property name="margin-top">10</property>
|
||||||
|
<property name="margin-bottom">10</property>
|
||||||
|
<property name="margin-start">10</property>
|
||||||
|
<property name="margin-end">10</property>
|
||||||
|
<style>
|
||||||
|
<class name="card"/>
|
||||||
|
</style>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="margin-start">10</property>
|
||||||
|
<property name="margin-end">10</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTextView" id="create_model_modelfile">
|
||||||
|
<style>
|
||||||
|
<class name="modelfile_textview"/>
|
||||||
|
</style>
|
||||||
|
<property name="wrap-mode">word</property>
|
||||||
|
<property name="top-margin">10</property>
|
||||||
|
<property name="bottom-margin">10</property>
|
||||||
|
<property name="hexpand">true</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label" translatable="yes">Some models require a modelfile, Alpaca fills FROM and SYSTEM (context) instructions automatically. Please visit the model's website or Ollama documentation for more information if you're unsure.</property>
|
||||||
|
<property name="margin-top">10</property>
|
||||||
|
<property name="margin-bottom">10</property>
|
||||||
|
<property name="margin-start">10</property>
|
||||||
|
<property name="margin-end">10</property>
|
||||||
|
<property name="halign">1</property>
|
||||||
|
<property name="wrap">true</property>
|
||||||
|
<style>
|
||||||
|
<class name="caption"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Create</property>
|
||||||
|
<signal name="clicked" handler="create_model_start"/>
|
||||||
|
<style>
|
||||||
|
<class name="suggested-action"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@@ -1032,6 +1033,10 @@ By downloading any model you accept their license agreement available on the mod
|
|||||||
<attribute name="label" translatable="yes">Import Chat</attribute>
|
<attribute name="label" translatable="yes">Import Chat</attribute>
|
||||||
<attribute name="action">app.import_chat</attribute>
|
<attribute name="action">app.import_chat</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Manage Models</attribute>
|
||||||
|
<attribute name="action">app.manage_models</attribute>
|
||||||
|
</item>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<item>
|
<item>
|
||||||
@@ -1085,9 +1090,13 @@ By downloading any model you accept their license agreement available on the mod
|
|||||||
<attribute name="action">app.create_model_from_existing</attribute>
|
<attribute name="action">app.create_model_from_existing</attribute>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">From GGUF File (Experimental)</attribute>
|
<attribute name="label" translatable="yes">From GGUF File</attribute>
|
||||||
<attribute name="action">app.create_model_from_file</attribute>
|
<attribute name="action">app.create_model_from_file</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">From Name</attribute>
|
||||||
|
<attribute name="action">app.create_model_from_name</attribute>
|
||||||
|
</item>
|
||||||
</section>
|
</section>
|
||||||
</menu>
|
</menu>
|
||||||
<object class="GtkFileFilter" id="file_filter_attachments">
|
<object class="GtkFileFilter" id="file_filter_attachments">
|
||||||
@@ -1130,7 +1139,7 @@ By downloading any model you accept their license agreement available on the mod
|
|||||||
<property name="title" translatable="yes">General</property>
|
<property name="title" translatable="yes">General</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><ctrl>Q</property>
|
<property name="accelerator"><ctrl>W</property>
|
||||||
<property name="title" translatable="yes">Close application</property>
|
<property name="title" translatable="yes">Close application</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@@ -1148,7 +1157,7 @@ By downloading any model you accept their license agreement available on the mod
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkShortcutsShortcut">
|
<object class="GtkShortcutsShortcut">
|
||||||
<property name="accelerator"><ctrl>P</property>
|
<property name="accelerator"><ctrl>comma</property>
|
||||||
<property name="title" translatable="yes">Preferences</property>
|
<property name="title" translatable="yes">Preferences</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
@@ -1164,6 +1173,18 @@ By downloading any model you accept their license agreement available on the mod
|
|||||||
<property name="title" translatable="yes">Show shortcuts window</property>
|
<property name="title" translatable="yes">Show shortcuts window</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkShortcutsShortcut">
|
||||||
|
<property name="accelerator"><ctrl>M</property>
|
||||||
|
<property name="title" translatable="yes">Manage models</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkShortcutsShortcut">
|
||||||
|
<property name="accelerator">F9</property>
|
||||||
|
<property name="title" translatable="yes">Toggle sidebar</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
|
"""
|
||||||
|
Moves the descriptions of models to src/available_models_descriptions.py
|
||||||
|
so they can be translated
|
||||||
|
"""
|
||||||
import json
|
import json
|
||||||
with open('src/available_models.json', 'r') as f:
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
with open('src/available_models.json', 'r', encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
results = 'descriptions = {\n'
|
RESULTS = 'descriptions = {\n'
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
results += f" '{key}': _(\"{value['description']}\"),\n"
|
RESULTS += f" '{key}': _(\"{value['description']}\"),\n"
|
||||||
results += '}'
|
RESULTS += '}'
|
||||||
with open('src/available_models_descriptions.py', 'w+') as f:
|
with open('src/available_models_descriptions.py', 'w+', encoding="utf-8") as f:
|
||||||
f.write(results)
|
f.write(RESULTS)
|
||||||
|
|||||||
Reference in New Issue
Block a user