55 Commits
2.6.0 ... main

Author SHA1 Message Date
hueso
13d1572dd5 Add CTRL+W shortcut to delete current chat 2024-10-19 10:55:25 -03:00
jeffser
f2fa417194 Fixed small mistake in brazilian translation 2024-10-18 19:38:45 -06:00
jeffser
4bf64c98e0 Added credits 2024-10-18 19:37:21 -06:00
Jeffry Samuel
6fb36b1cc4 Added new credit 2024-10-18 19:36:07 -06:00
Bruno Antunes
89b600f964 Translated somes strings to PT-BR (#357)
* Translated somes strings to PT-BR

* Translated strings to PT-BR 2/2
2024-10-18 19:35:21 -06:00
Louis Chauvet-Villaret
91c54a4565 French updated (#353) 2024-10-17 14:55:24 -06:00
jeffser
c3b105c30b changed when translated caption option appears 2024-10-17 12:42:47 -06:00
jeffser
7c4c1e0997 Better implementation of auto-translate captions 2024-10-17 00:18:18 -06:00
jeffser
f50c98befc Changed style for attachment preview dialog 2024-10-17 00:17:46 -06:00
jeffser
98a0f60be9 Brute forcing pytube into working 2024-10-16 23:47:12 -06:00
jeffser
d9e6b08fd7 Fixed auto translate message 2024-10-16 23:24:10 -06:00
jeffser
d36f6b6644 Fix youtube integration 2024-10-16 23:20:08 -06:00
jeffser
e67d0bea83 Fix youtube integration 2024-10-16 23:19:34 -06:00
jeffser
218c10f4ad Rewrote system for getting youtube transcripts 2024-10-16 14:47:56 -06:00
Simon
134a907eff Update uk.po (#352) 2024-10-16 10:01:23 -06:00
aritra saha
97ee2e7a24 update hipo bnpo (#351)
* Update bn.po

* Update hi.po
2024-10-16 10:01:02 -06:00
jeffser
ed62aed6a4 Updated spanish 2024-10-15 21:48:17 -06:00
jeffser
b6ab989ac8 Updated translations 2024-10-15 21:46:53 -06:00
jeffser
cefd758846 Preparing for 2.7.0 2024-10-15 21:46:27 -06:00
jeffser
c114ae67ba Made messages more compact 2024-10-15 20:06:05 -06:00
jeffser
0a7f7e5ac2 User messages are now smaller 2024-10-15 11:24:44 -06:00
jeffser
22db4a43d9 Made messages more compact 2024-10-15 10:57:49 -06:00
jeffser
70e4d8f407 Fixed 'window size not adapting to large text' 2024-10-15 10:31:00 -06:00
jeffser
3da5207f53 Finally finished label 2024-10-14 16:43:46 -06:00
jeffser
f00122d789 Label thingy 2024-10-14 16:41:04 -06:00
jeffser
6a19ca266e Added dots 2024-10-14 16:38:31 -06:00
jeffser
4f9aebf7a3 Updated label 2024-10-14 16:36:07 -06:00
jeffser
61f9e187bd Added link to AMD Support label 2024-10-14 16:28:13 -06:00
jeffser
27126736a4 Fixed reconnection dialog not selecting 'use local instance' 2024-10-14 15:59:15 -06:00
aritra saha
c9cf2bfefc update hipo and small bn.po (#348)
* Update bn.po

* Update bn.po

* Update hi.po
2024-10-14 15:46:41 -06:00
aritra saha
e03ea42be3 Update bn.po (#347) 2024-10-13 17:26:40 -06:00
jeffser
3253e67680 Update to snap 2024-10-13 17:08:42 -06:00
jeffser
e189769f3f Updated spanish 2024-10-13 17:07:33 -06:00
jeffser
f6637493db Update languages 2024-10-13 17:03:00 -06:00
jeffser
063da38597 Preparing for 2.6.5 2024-10-13 16:24:20 -06:00
jeffser
7587b03828 Added Exception as e so it can catch everything 2024-10-13 15:21:39 -06:00
jeffser
8fffb64f79 the 2024-10-13 15:07:20 -06:00
aritra saha
735eae0d0e Update bn.po (#346)
* Update bn.po

* Update bn.po
2024-10-13 14:39:45 -06:00
jeffser
8c98be6ef6 Integrated instance indicator on preferences 2024-10-13 10:27:18 -06:00
jeffser
115e22e52c Added warning if model is too large for system 2024-10-13 10:00:43 -06:00
jeffser
792a81ad03 Restore Ollama logging 2024-10-13 09:57:52 -06:00
jeffser
2ea0ff6870 Removed webiste button in creation page 2024-10-12 18:47:20 -06:00
jeffser
6242087152 Replace model selector with button if there aren't any models download 2024-10-12 17:25:34 -06:00
aritra saha
1da6e31de1 Update hi.po (#345) 2024-10-12 17:07:09 -06:00
aritra saha
cb4979ab7c Update bn.po (#344)
* Update bn.po

* Update bn.po
2024-10-12 17:06:55 -06:00
jeffser
da653c754d Added create button to details and fixed some css 2024-10-12 17:01:54 -06:00
jeffser
c4907b81fd Fixed datetime bug 2024-10-12 13:29:54 -06:00
jeffser
4c104560d5 Chaged styling 2024-10-11 22:51:51 -06:00
jeffser
2253e378ac Updated spanish 2024-10-11 22:45:20 -06:00
jeffser
ba66ac40a3 Updated translations 2024-10-11 22:44:53 -06:00
jeffser
40d0d92498 Forgot one translation 2024-10-11 22:44:22 -06:00
jeffser
4ed6cf8e18 Updated spanish 2024-10-11 22:42:49 -06:00
jeffser
3fc1c74f51 Updated translations 2024-10-11 22:41:17 -06:00
jeffser
150e8779c7 New 'model details' page 2024-10-11 22:40:31 -06:00
Jeffry Samuel
5462248565 Update README.md 2024-10-11 19:08:35 -06:00
29 changed files with 9320 additions and 7847 deletions

View File

@@ -33,7 +33,7 @@ Alpaca is an [Ollama](https://github.com/ollama/ollama) client where you can man
Normal conversation | Image recognition | Code highlighting | YouTube transcription | Model management Normal conversation | Image recognition | Code highlighting | YouTube transcription | Model management
:------------------:|:-----------------:|:-----------------:|:---------------------:|:----------------: :------------------:|:-----------------:|:-----------------:|:---------------------:|:----------------:
![screenie1](https://jeffser.com/images/alpaca/screenie1.png) | ![screenie2](https://jeffser.com/images/alpaca/screenie2.png) | ![screenie3](https://jeffser.com/images/alpaca/screenie3.png) | ![screenie4](https://jeffser.com/images/alpaca/screenie4.png) | ![screenie5](https://jeffser.com/images/alpaca/screenie5.png) ![screenie1](https://jeffser.com/images/alpaca/screenie1.png) | ![screenie2](https://jeffser.com/images/alpaca/screenie2.png) | ![screenie3](https://jeffser.com/images/alpaca/screenie3.png) | ![screenie4](https://jeffser.com/images/alpaca/screenie5.png) | ![screenie5](https://jeffser.com/images/alpaca/screenie6.png)
## Installation ## Installation
@@ -68,7 +68,7 @@ Language | Contributors
🇷🇺 Russian | [Alex K](https://github.com/alexkdeveloper) 🇷🇺 Russian | [Alex K](https://github.com/alexkdeveloper)
🇪🇸 Spanish | [Jeffry Samuel](https://github.com/jeffser) 🇪🇸 Spanish | [Jeffry Samuel](https://github.com/jeffser)
🇫🇷 French | [Louis Chauvet-Villaret](https://github.com/loulou64490) , [Théo FORTIN](https://github.com/topiga) 🇫🇷 French | [Louis Chauvet-Villaret](https://github.com/loulou64490) , [Théo FORTIN](https://github.com/topiga)
🇧🇷 Brazilian Portuguese | [Daimar Stein](https://github.com/not-a-dev-stein) 🇧🇷 Brazilian Portuguese | [Daimar Stein](https://github.com/not-a-dev-stein) , [Bruno Antunes](https://github.com/antun3s)
🇳🇴 Norwegian | [CounterFlow64](https://github.com/CounterFlow64) 🇳🇴 Norwegian | [CounterFlow64](https://github.com/CounterFlow64)
🇮🇳 Bengali | [Aritra Saha](https://github.com/olumolu) 🇮🇳 Bengali | [Aritra Saha](https://github.com/olumolu)
🇨🇳 Simplified Chinese | [Yuehao Sui](https://github.com/8ar10der) , [Aleksana](https://github.com/Aleksanaa) 🇨🇳 Simplified Chinese | [Yuehao Sui](https://github.com/8ar10der) , [Aleksana](https://github.com/Aleksanaa)

View File

@@ -111,6 +111,45 @@
} }
] ]
}, },
{
"name": "python3-youtube-transcript-api",
"buildsystem": "simple",
"build-commands": [
"pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"youtube-transcript-api\" --no-build-isolation"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl",
"sha256": "922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz",
"sha256": "223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl",
"sha256": "946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl",
"sha256": "70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl",
"sha256": "ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"
},
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/52/42/5f57d37d56bdb09722f226ed81cc1bec63942da745aa27266b16b0e16a5d/youtube_transcript_api-0.6.2-py3-none-any.whl",
"sha256": "019dbf265c6a68a0591c513fff25ed5a116ce6525832aefdfb34d4df5567121c"
}
]
},
{ {
"name": "python3-html2text", "name": "python3-html2text",
"buildsystem": "simple", "buildsystem": "simple",

View File

@@ -82,6 +82,32 @@
<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="2.7.0" date="2024-10-15">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/2.7.0</url>
<description>
<p>New</p>
<ul>
<li>User messages are now compacted into bubbles</li>
</ul>
<p>Fixes</p>
<ul>
<li>Fixed re connection dialog not working when 'use local instance' is selected</li>
<li>Fixed model manager not adapting to large system fonts</li>
</ul>
</description>
</release>
<release version="2.6.5" date="2024-10-13">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/2.6.5</url>
<description>
<p>New</p>
<ul>
<li>Details page for models</li>
<li>Model selector gets replaced with 'manage models' button when there are no models downloaded</li>
<li>Added warning when model is too big for the device</li>
<li>Added AMD GPU indicator in preferences</li>
</ul>
</description>
</release>
<release version="2.6.0" date="2024-10-11"> <release version="2.6.0" date="2024-10-11">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/2.6.0</url> <url type="details">https://github.com/Jeffser/Alpaca/releases/tag/2.6.0</url>
<description> <description>

View File

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

File diff suppressed because it is too large Load Diff

1138
po/bn.po

File diff suppressed because it is too large Load Diff

1096
po/de.po

File diff suppressed because it is too large Load Diff

1107
po/es.po

File diff suppressed because it is too large Load Diff

1246
po/fr.po

File diff suppressed because it is too large Load Diff

1096
po/he.po

File diff suppressed because it is too large Load Diff

1137
po/hi.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1093
po/ru.po

File diff suppressed because it is too large Load Diff

1096
po/te.po

File diff suppressed because it is too large Load Diff

1096
po/tr.po

File diff suppressed because it is too large Load Diff

1262
po/uk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
name: alpaca name: jeffser-alpaca
base: core24 base: core24
adopt-info: alpaca adopt-info: alpaca
@@ -63,14 +63,15 @@ parts:
ollama: ollama:
plugin: dump plugin: dump
source: source:
- on amd64: https://github.com/ollama/ollama/releases/download/v0.3.10/ollama-linux-amd64.tgz - on amd64: https://github.com/ollama/ollama/releases/download/v0.3.12/ollama-linux-amd64.tgz
- on arm64: https://github.com/ollama/ollama/releases/download/v0.3.10/ollama-linux-arm64.tgz - on arm64: https://github.com/ollama/ollama/releases/download/v0.3.12/ollama-linux-arm64.tgz
# Alpaca app # Alpaca app
alpaca: alpaca:
plugin: meson plugin: meson
source-type: git source-type: git
source: https://github.com/Jeffser/Alpaca.git source: https://github.com/Jeffser/Alpaca.git
source-tag: 2.6.5
source-depth: 1 source-depth: 1
meson-parameters: meson-parameters:
- --prefix=/snap/alpaca/current/usr - --prefix=/snap/alpaca/current/usr

View File

@@ -33,6 +33,7 @@
<file alias="icons/scalable/status/chat-bubble-text-symbolic.svg">icons/chat-bubble-text-symbolic.svg</file> <file alias="icons/scalable/status/chat-bubble-text-symbolic.svg">icons/chat-bubble-text-symbolic.svg</file>
<file alias="icons/scalable/status/execute-from-symbolic.svg">icons/execute-from-symbolic.svg</file> <file alias="icons/scalable/status/execute-from-symbolic.svg">icons/execute-from-symbolic.svg</file>
<file alias="icons/scalable/status/cross-large-symbolic.svg">icons/cross-large-symbolic.svg</file> <file alias="icons/scalable/status/cross-large-symbolic.svg">icons/cross-large-symbolic.svg</file>
<file alias="icons/scalable/status/info-outline-symbolic.svg">icons/info-outline-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>

View File

@@ -11,15 +11,28 @@ logger = getLogger(__name__)
window = None window = None
AMD_support_label = "\n<a href='https://github.com/Jeffser/Alpaca/wiki/AMD-Support'>{}</a>".format(_('Alpaca Support'))
def log_output(pipe): def log_output(pipe):
with open(os.path.join(data_dir, 'tmp.log'), 'a') as f: with open(os.path.join(data_dir, 'tmp.log'), 'a') as f:
with pipe: with pipe:
try: try:
for line in iter(pipe.readline, ''): for line in iter(pipe.readline, ''):
#print(line, end='') print(line, end='')
f.write(line) f.write(line)
f.flush() f.flush()
except: if 'msg="model request too large for system"' in line:
window.show_toast(_("Model request too large for system"), window.main_overlay)
elif 'msg="amdgpu detected, but no compatible rocm library found.' in line:
if bool(os.getenv("FLATPAK_ID")):
window.ollama_information_label.set_label(_("AMD GPU detected but the extension is missing, Ollama will use CPU.") + AMD_support_label)
else:
window.ollama_information_label.set_label(_("AMD GPU detected but ROCm is missing, Ollama will use CPU.") + AMD_support_label)
window.ollama_information_label.set_css_classes(['dim-label', 'error'])
elif 'msg="amdgpu is supported"' in line:
window.ollama_information_label.set_label(_("Using AMD GPU type '{}'").format(line.split('=')[-1]))
window.ollama_information_label.set_css_classes(['dim-label', 'success'])
except Exception as e:
pass pass
class instance(): class instance():
@@ -116,6 +129,8 @@ class instance():
self.instance = instance self.instance = instance
if not self.idle_timer: if not self.idle_timer:
self.start_timer() self.start_timer()
window.ollama_information_label.set_label(_("Integrated Ollama instance is running"))
window.ollama_information_label.set_css_classes(['dim-label', 'success'])
else: else:
self.remote = True self.remote = True
window.remote_connection_switch.set_sensitive(True) window.remote_connection_switch.set_sensitive(True)
@@ -130,6 +145,8 @@ class instance():
self.instance.terminate() self.instance.terminate()
self.instance.wait() self.instance.wait()
self.instance = None self.instance = None
window.ollama_information_label.set_label(_("Integrated Ollama instance is not running"))
window.ollama_information_label.set_css_classes(['dim-label'])
logger.info("Stopped Alpaca's Ollama instance") logger.info("Stopped Alpaca's Ollama instance")
def reset(self): def reset(self):

View File

@@ -66,7 +66,8 @@ class chat(Gtk.ScrolledWindow):
vexpand=True, vexpand=True,
hexpand=True, hexpand=True,
css_classes=["undershoot-bottom"], css_classes=["undershoot-bottom"],
name=name name=name,
hscrollbar_policy=2
) )
self.messages = {} self.messages = {}
self.welcome_screen = None self.welcome_screen = None

View File

@@ -230,7 +230,8 @@ class attachment_container(Gtk.ScrolledWindow):
self.container = Gtk.Box( self.container = Gtk.Box(
orientation=0, orientation=0,
spacing=12 spacing=10,
valign=1
) )
super().__init__( super().__init__(
@@ -238,7 +239,8 @@ class attachment_container(Gtk.ScrolledWindow):
margin_start=10, margin_start=10,
margin_end=10, margin_end=10,
hexpand=True, hexpand=True,
child=self.container child=self.container,
vscrollbar_policy=2
) )
def add_file(self, file:attachment): def add_file(self, file:attachment):
@@ -467,10 +469,15 @@ class message(Gtk.Overlay):
orientation=1, orientation=1,
halign='fill', halign='fill',
css_classes=["response_message"] if self.bot else ["card", "user_message"], css_classes=["response_message"] if self.bot else ["card", "user_message"],
spacing=12 spacing=5,
width_request=-1 if self.bot else 375
) )
super().__init__(css_classes=["message"], name=message_id) super().__init__(
css_classes=["message"],
name=message_id,
halign=0 if self.bot else 2
)
self.set_child(self.container) self.set_child(self.container)
def add_attachments(self, attachments:dict): def add_attachments(self, attachments:dict):
@@ -619,7 +626,7 @@ class message(Gtk.Overlay):
if self.spinner: if self.spinner:
self.container.remove(self.spinner) self.container.remove(self.spinner)
self.spinner = None self.spinner = None
self.spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True) self.spinner = Gtk.Spinner(spinning=True, margin_top=10, margin_bottom=10, hexpand=True)
self.container.append(self.spinner) self.container.append(self.spinner)
self.container.append(text_b) self.container.append(text_b)
self.container.queue_draw() self.container.queue_draw()

View File

@@ -56,7 +56,7 @@ class model_selector_popup(Gtk.Popover):
class model_selector_row(Gtk.ListBoxRow): class model_selector_row(Gtk.ListBoxRow):
__gtype_name__ = 'AlpacaModelSelectorRow' __gtype_name__ = 'AlpacaModelSelectorRow'
def __init__(self, model_name:str, image_recognition:bool): def __init__(self, model_name:str, data:dict):
super().__init__( super().__init__(
child = Gtk.Label( child = Gtk.Label(
label=window.convert_model_name(model_name, 0), label=window.convert_model_name(model_name, 0),
@@ -68,7 +68,8 @@ class model_selector_row(Gtk.ListBoxRow):
name=model_name, name=model_name,
tooltip_text=window.convert_model_name(model_name, 0) tooltip_text=window.convert_model_name(model_name, 0)
) )
self.image_recognition = image_recognition self.data = data
self.image_recognition = 'projector_info' in self.data
class model_selector_button(Gtk.MenuButton): class model_selector_button(Gtk.MenuButton):
__gtype_name__ = 'AlpacaModelSelectorButton' __gtype_name__ = 'AlpacaModelSelectorButton'
@@ -81,11 +82,10 @@ class model_selector_button(Gtk.MenuButton):
orientation=0, orientation=0,
spacing=5 spacing=5
) )
self.label = Gtk.Label(label=_('Select a Model')) self.label = Gtk.Label()
container.append(self.label) container.append(self.label)
container.append(Gtk.Image.new_from_icon_name("down-symbolic")) container.append(Gtk.Image.new_from_icon_name("down-symbolic"))
super().__init__( super().__init__(
tooltip_text=_('Select a Model'),
child=container, child=container,
popover=self.popover, popover=self.popover,
halign=3 halign=3
@@ -104,27 +104,28 @@ class model_selector_button(Gtk.MenuButton):
self.label.set_label(window.convert_model_name(model_name, 0)) self.label.set_label(window.convert_model_name(model_name, 0))
self.set_tooltip_text(window.convert_model_name(model_name, 0)) self.set_tooltip_text(window.convert_model_name(model_name, 0))
elif len(list(listbox)) == 0: elif len(list(listbox)) == 0:
self.label.set_label(_("Select a Model")) window.title_stack.set_visible_child_name('no_models')
self.set_tooltip_text(_("Select a Model"))
window.model_manager.verify_if_image_can_be_used() window.model_manager.verify_if_image_can_be_used()
def add_model(self, model_name:str): def add_model(self, model_name:str):
vision = False data = None
response = window.ollama_instance.request("POST", "api/show", json.dumps({"name": model_name})) response = window.ollama_instance.request("POST", "api/show", json.dumps({"name": model_name}))
if response.status_code != 200: if response.status_code != 200:
logger.error(f"Status code was {response.status_code}") logger.error(f"Status code was {response.status_code}")
return return
try: try:
vision = 'projector_info' in json.loads(response.text) data = json.loads(response.text)
except Exception as e: except Exception as e:
logger.error(f"Error fetching vision info: {str(e)}") logger.error(f"Error fetching 'api - show' info: {str(e)}")
model_row = model_selector_row(model_name, vision) model_row = model_selector_row(model_name, data)
GLib.idle_add(self.get_popover().model_list_box.append, model_row) GLib.idle_add(self.get_popover().model_list_box.append, model_row)
GLib.idle_add(self.change_model, model_name) GLib.idle_add(self.change_model, model_name)
GLib.idle_add(window.title_stack.set_visible_child_name, 'model_selector')
def remove_model(self, model_name:str): def remove_model(self, model_name:str):
self.get_popover().model_list_box.remove(next((model for model in list(self.get_popover().model_list_box) if model.get_name() == model_name), None)) self.get_popover().model_list_box.remove(next((model for model in list(self.get_popover().model_list_box) if model.get_name() == model_name), None))
self.model_changed(self.get_popover().model_list_box) self.model_changed(self.get_popover().model_list_box)
window.title_stack.set_visible_child_name('model_selector' if len(window.model_manager.get_model_list()) > 0 else 'no_models')
def clear_list(self): def clear_list(self):
self.get_popover().model_list_box.remove_all() self.get_popover().model_list_box.remove_all()
@@ -248,6 +249,37 @@ class pulling_model_list(Gtk.ListBox):
visible=False visible=False
) )
class information_bow(Gtk.Box):
__gtype_name__ = 'AlpacaModelInformationBow'
def __init__(self, title:str, subtitle:str):
self.title = title
self.subtitle = subtitle
title_label = Gtk.Label(
label=self.title,
css_classes=['subtitle', 'caption', 'dim-label'],
hexpand=True,
margin_top=10,
margin_start=0,
margin_end=0
)
subtitle_label = Gtk.Label(
label=self.subtitle if self.subtitle else '(none)',
css_classes=['heading'],
hexpand=True,
margin_bottom=10,
margin_start=0,
margin_end=0
)
super().__init__(
spacing=5,
orientation=1,
css_classes=['card']
)
self.append(title_label)
self.append(subtitle_label)
class local_model(Gtk.ListBoxRow): class local_model(Gtk.ListBoxRow):
__gtype_name__ = 'AlpacaLocalModel' __gtype_name__ = 'AlpacaLocalModel'
@@ -275,6 +307,16 @@ class local_model(Gtk.ListBoxRow):
description_box.append(model_label) description_box.append(model_label)
description_box.append(tag_label) description_box.append(tag_label)
info_button = Gtk.Button(
icon_name = "info-outline-symbolic",
vexpand = False,
valign = 3,
css_classes = ["circular"],
tooltip_text = _("Details")
)
info_button.connect('clicked', self.show_information)
delete_button = Gtk.Button( delete_button = Gtk.Button(
icon_name = "user-trash-symbolic", icon_name = "user-trash-symbolic",
vexpand = False, vexpand = False,
@@ -302,6 +344,7 @@ class local_model(Gtk.ListBoxRow):
margin_end=10 margin_end=10
) )
container_box.append(description_box) container_box.append(description_box)
container_box.append(info_button)
container_box.append(delete_button) container_box.append(delete_button)
super().__init__( super().__init__(
@@ -309,6 +352,53 @@ class local_model(Gtk.ListBoxRow):
name=model_name name=model_name
) )
def show_information(self, button):
model = next((element for element in list(window.model_manager.model_selector.get_popover().model_list_box) if element.get_name() == self.get_name()), None)
model_name = model.get_child().get_label()
window.model_detail_page.set_title(' ('.join(model_name.split(' (')[:-1]))
window.model_detail_page.set_description(' ('.join(model_name.split(' (')[-1:])[:-1])
window.model_detail_create_button.set_name(model_name)
window.model_detail_create_button.set_tooltip_text(_("Create Model Based on '{}'").format(model_name))
details_flow_box = Gtk.FlowBox(
valign=1,
hexpand=True,
vexpand=False,
selection_mode=0,
max_children_per_line=2,
min_children_per_line=1,
margin_top=12,
margin_bottom=12,
margin_start=12,
margin_end=12
)
translation_strings={
'modified_at': _('Modified At'),
'parent_model': _('Parent Model'),
'format': _('Format'),
'family': _('Family'),
'parameter_size': _('Parameter Size'),
'quantization_level': _('Quantization Level')
}
if 'modified_at' in model.data and model.data['modified_at']:
details_flow_box.append(information_bow(
title=translation_strings['modified_at'],
subtitle=datetime.datetime.strptime(':'.join(model.data['modified_at'].split(':')[:2]), '%Y-%m-%dT%H:%M').strftime('%Y-%m-%d %H:%M')
))
for name, value in model.data['details'].items():
if isinstance(value, str):
details_flow_box.append(information_bow(
title=translation_strings[name] if name in translation_strings else name.replace('_', ' ').title(),
subtitle=value
))
window.model_detail_page.set_child(details_flow_box)
window.navigation_view_manage_models.push_by_tag('model_information')
class local_model_list(Gtk.ListBox): class local_model_list(Gtk.ListBox):
__gtype_name__ = 'AlpacaLocalModelList' __gtype_name__ = 'AlpacaLocalModelList'
@@ -341,7 +431,9 @@ class available_model(Gtk.ListBoxRow):
label="<b>{}</b> <small>by {}</small>".format(self.model_title, self.model_author), label="<b>{}</b> <small>by {}</small>".format(self.model_title, self.model_author),
hexpand=True, hexpand=True,
halign=1, halign=1,
use_markup=True use_markup=True,
wrap=True,
wrap_mode=0
) )
description_label = Gtk.Label( description_label = Gtk.Label(
css_classes=["subtitle"], css_classes=["subtitle"],
@@ -521,7 +613,8 @@ class model_manager_container(Gtk.Box):
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
window.connection_error() window.connection_error()
window.title_stack.set_visible_child_name('model_selector') window.title_stack.set_visible_child_name('model_selector' if len(window.model_manager.get_model_list()) > 0 else 'no_models')
#window.title_stack.set_visible_child_name('model_selector')
window.chat_list_box.update_welcome_screens(len(self.get_model_list()) > 0) window.chat_list_box.update_welcome_screens(len(self.get_model_list()) > 0)
#Should only be called when the app starts #Should only be called when the app starts

View File

@@ -4,7 +4,7 @@ Working on organizing the code
""" """
import os, requests import os, requests
from pytube import YouTube from youtube_transcript_api import YouTubeTranscriptApi
from html2text import html2text from html2text import html2text
from .internal import cache_dir from .internal import cache_dir
@@ -18,25 +18,36 @@ def connect_remote(remote_url:str, bearer_token:str):
window.model_manager.update_local_list() window.model_manager.update_local_list()
window.save_server_config() window.save_server_config()
def attach_youtube(video_url:str, caption_name:str): def attach_youtube(video_title:str, video_author:str, watch_url:str, video_url:str, video_id:str, caption_name:str):
buffer = window.message_text_view.get_buffer() buffer = window.message_text_view.get_buffer()
text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False).replace(video_url, "") text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False).replace(video_url, "")
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter()) buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
buffer.insert(buffer.get_start_iter(), text, len(text)) buffer.insert(buffer.get_start_iter(), text, len(text))
yt = YouTube(video_url) result_text = "{}\n{}\n{}\n\n".format(video_title, video_author, watch_url)
text = "{}\n{}\n{}\n\n".format(yt.title, yt.author, yt.watch_url) caption_name = caption_name.split(' (')[-1][:-1]
if caption_name.startswith('Translate:'):
available_captions = get_youtube_transcripts(video_id)
original_caption_name = available_captions[0].split(' (')[-1][:-1]
transcript = YouTubeTranscriptApi.list_transcripts(video_id).find_transcript([original_caption_name]).translate(caption_name.split(':')[-1]).fetch()
result_text += '(Auto translated from {})\n'.format(available_captions[0])
else:
transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=[caption_name])
result_text += '\n'.join([t['text'] for t in transcript])
for event in yt.captions[caption_name.split('(')[-1][:-1]].json_captions['events']:
text += "{}\n".format(event['segs'][0]['utf8'].replace('\n', '\\n'))
if not os.path.exists(os.path.join(cache_dir, 'tmp/youtube')): if not os.path.exists(os.path.join(cache_dir, 'tmp/youtube')):
os.makedirs(os.path.join(cache_dir, 'tmp/youtube')) os.makedirs(os.path.join(cache_dir, 'tmp/youtube'))
file_path = os.path.join(os.path.join(cache_dir, 'tmp/youtube'), f'{yt.title} ({caption_name.split(" (")[0]})') file_path = os.path.join(os.path.join(cache_dir, 'tmp/youtube'), '{} ({})'.format(video_title.replace('/', ' '), caption_name))
with open(file_path, 'w+', encoding="utf-8") as f: with open(file_path, 'w+', encoding="utf-8") as f:
f.write(text) f.write(result_text)
window.attach_file(file_path, 'youtube') window.attach_file(file_path, 'youtube')
def get_youtube_transcripts(video_id:str):
return ['{} ({})'.format(t.language, t.language_code) for t in YouTubeTranscriptApi.list_transcripts(video_id)]
def attach_website(url:str): def attach_website(url:str):
response = requests.get(url) response = requests.get(url)
if response.status_code == 200: if response.status_code == 200:

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 0 c -4.410156 0 -8 3.589844 -8 8 s 3.589844 8 8 8 s 8 -3.589844 8 -8 s -3.589844 -8 -8 -8 z m 0 2 c 3.332031 0 6 2.667969 6 6 s -2.667969 6 -6 6 s -6 -2.667969 -6 -6 s 2.667969 -6 6 -6 z m 0 1.875 c -0.621094 0 -1.125 0.503906 -1.125 1.125 s 0.503906 1.125 1.125 1.125 s 1.125 -0.503906 1.125 -1.125 s -0.503906 -1.125 -1.125 -1.125 z m -1.523438 3.125 c -0.265624 0.011719 -0.476562 0.230469 -0.476562 0.5 c 0 0.277344 0.222656 0.5 0.5 0.5 h 0.5 v 3 h -0.5 c -0.277344 0 -0.5 0.222656 -0.5 0.5 s 0.222656 0.5 0.5 0.5 h 3 c 0.277344 0 0.5 -0.222656 0.5 -0.5 s -0.222656 -0.5 -0.5 -0.5 h -0.5 v -4 h -2.5 c -0.007812 0 -0.015625 0 -0.023438 0 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 813 B

View File

@@ -40,6 +40,7 @@ translators = [
'Louis Chauvet-Villaret (French) https://github.com/loulou64490', 'Louis Chauvet-Villaret (French) https://github.com/loulou64490',
'Théo FORTIN (French) https://github.com/topiga', 'Théo FORTIN (French) https://github.com/topiga',
'Daimar Stein (Brazilian Portuguese) https://github.com/not-a-dev-stein', 'Daimar Stein (Brazilian Portuguese) https://github.com/not-a-dev-stein',
'Bruno Antunes (Brazilian Portuguese) https://github.com/antun3s',
'CounterFlow64 (Norwegian) https://github.com/CounterFlow64', 'CounterFlow64 (Norwegian) https://github.com/CounterFlow64',
'Aritra Saha (Bengali) https://github.com/olumolu', 'Aritra Saha (Bengali) https://github.com/olumolu',
'Yuehao Sui (Simplified Chinese) https://github.com/8ar10der', 'Yuehao Sui (Simplified Chinese) https://github.com/8ar10der',
@@ -56,7 +57,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.props.active_window.closing_app(None), ['<primary>w', '<primary>q']) self.create_action('quit', lambda *_: self.props.active_window.closing_app(None), ['<primary>q'])
self.set_accels_for_action('app.delete_current_chat', ['<primary>w'])
self.create_action('preferences', lambda *_: self.props.active_window.preferences_dialog.present(self.props.active_window), ['<primary>comma']) self.create_action('preferences', lambda *_: self.props.active_window.preferences_dialog.present(self.props.active_window), ['<primary>comma'])
self.create_action('about', self.on_about_action) self.create_action('about', self.on_about_action)
self.set_accels_for_action("win.show-help-overlay", ['<primary>slash']) self.set_accels_for_action("win.show-help-overlay", ['<primary>slash'])

View File

@@ -69,7 +69,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
preferences_dialog = Gtk.Template.Child() preferences_dialog = Gtk.Template.Child()
shortcut_window : Gtk.ShortcutsWindow = Gtk.Template.Child() shortcut_window : Gtk.ShortcutsWindow = Gtk.Template.Child()
file_preview_dialog = Gtk.Template.Child() file_preview_dialog = Gtk.Template.Child()
file_preview_text_view = Gtk.Template.Child() file_preview_text_label = Gtk.Template.Child()
file_preview_image = Gtk.Template.Child() file_preview_image = Gtk.Template.Child()
welcome_dialog = Gtk.Template.Child() welcome_dialog = Gtk.Template.Child()
welcome_carousel = Gtk.Template.Child() welcome_carousel = Gtk.Template.Child()
@@ -102,6 +102,9 @@ class AlpacaWindow(Adw.ApplicationWindow):
title_stack = Gtk.Template.Child() title_stack = Gtk.Template.Child()
manage_models_dialog = Gtk.Template.Child() manage_models_dialog = Gtk.Template.Child()
model_scroller = Gtk.Template.Child() model_scroller = Gtk.Template.Child()
model_detail_page = Gtk.Template.Child()
model_detail_create_button = Gtk.Template.Child()
ollama_information_label = Gtk.Template.Child()
chat_list_container = Gtk.Template.Child() chat_list_container = Gtk.Template.Child()
chat_list_box = None chat_list_box = None
@@ -332,6 +335,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
clipboard.read_text_async(None, self.cb_text_received) clipboard.read_text_async(None, self.cb_text_received)
clipboard.read_texture_async(None, self.cb_image_received) clipboard.read_texture_async(None, self.cb_image_received)
@Gtk.Template.Callback()
def model_detail_create_button_clicked(self, button):
self.create_model(button.get_name(), False)
def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag
try: try:
if mode == 0: if mode == 0:
@@ -351,20 +358,15 @@ class AlpacaWindow(Adw.ApplicationWindow):
modelfile_buffer.delete(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter()) modelfile_buffer.delete(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter())
self.create_model_system.set_text('') self.create_model_system.set_text('')
if not file: if not file:
response = self.ollama_instance.request("POST", "api/show", json.dumps({"name": self.convert_model_name(model, 1)})) data = next((element for element in list(self.model_manager.model_selector.get_popover().model_list_box) if element.get_name() == self.convert_model_name(model, 1)), None).data
if response.status_code == 200: modelfile = []
data = json.loads(response.text) for line in data['modelfile'].split('\n'):
modelfile = [] if line.startswith('SYSTEM'):
for line in data['modelfile'].split('\n'): self.create_model_system.set_text(line[len('SYSTEM'):].strip())
if line.startswith('SYSTEM'): if not line.startswith('SYSTEM') and not line.startswith('FROM') and not line.startswith('#'):
self.create_model_system.set_text(line[len('SYSTEM'):].strip()) modelfile.append(line)
if not line.startswith('SYSTEM') and not line.startswith('FROM') and not line.startswith('#'): self.create_model_name.set_text(self.convert_model_name(model, 1).split(':')[0] + "-custom")
modelfile.append(line) modelfile_buffer.insert(modelfile_buffer.get_start_iter(), '\n'.join(modelfile), len('\n'.join(modelfile).encode('utf-8')))
self.create_model_name.set_text(self.convert_model_name(model, 1).split(':')[0] + "-custom")
modelfile_buffer.insert(modelfile_buffer.get_start_iter(), '\n'.join(modelfile), len('\n'.join(modelfile).encode('utf-8')))
else:
##TODO ERROR MESSAGE
return
self.create_model_base.set_subtitle(self.convert_model_name(model, 1)) self.create_model_base.set_subtitle(self.convert_model_name(model, 1))
else: else:
self.create_model_name.set_text(os.path.splitext(os.path.basename(model))[0]) self.create_model_name.set_text(os.path.splitext(os.path.basename(model))[0])
@@ -403,7 +405,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
if content: if content:
if file_type == 'image': if file_type == 'image':
self.file_preview_image.set_visible(True) self.file_preview_image.set_visible(True)
self.file_preview_text_view.set_visible(False) self.file_preview_text_label.set_visible(False)
image_data = base64.b64decode(content) image_data = base64.b64decode(content)
loader = GdkPixbuf.PixbufLoader.new() loader = GdkPixbuf.PixbufLoader.new()
loader.write(image_data) loader.write(image_data)
@@ -416,10 +418,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.file_preview_open_button.set_name(file_path) self.file_preview_open_button.set_name(file_path)
else: else:
self.file_preview_image.set_visible(False) self.file_preview_image.set_visible(False)
self.file_preview_text_view.set_visible(True) self.file_preview_text_label.set_visible(True)
buffer = self.file_preview_text_view.get_buffer() buffer = self.file_preview_text_label.set_label(content)
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
buffer.insert(buffer.get_start_iter(), content, len(content.encode('utf-8')))
if file_type == 'youtube': if file_type == 'youtube':
self.file_preview_dialog.set_title(content.split('\n')[0]) self.file_preview_dialog.set_title(content.split('\n')[0])
self.file_preview_open_button.set_name(content.split('\n')[2]) self.file_preview_open_button.set_name(content.split('\n')[2])
@@ -607,6 +607,7 @@ Generate a title following these rules:
self.chat_list_box.prepend_chat(_("New Chat")) self.chat_list_box.prepend_chat(_("New Chat"))
def generate_numbered_name(self, chat_name:str, compare_list:list) -> str: def generate_numbered_name(self, chat_name:str, compare_list:list) -> str:
if chat_name in compare_list: if chat_name in compare_list:
for i in range(len(compare_list)): for i in range(len(compare_list)):
@@ -628,7 +629,7 @@ Generate a title following these rules:
if self.ollama_instance.remote: if self.ollama_instance.remote:
options = { options = {
_("Close Alpaca"): {"callback": lambda *_: self.get_application().quit(), "appearance": "destructive"}, _("Close Alpaca"): {"callback": lambda *_: self.get_application().quit(), "appearance": "destructive"},
_("Use Local Instance"): {"callback": lambda *_: window.remote_connection_switch.set_active(False)}, _("Use Local Instance"): {"callback": lambda *_: self.remote_connection_switch.set_active(False)},
_("Connect"): {"callback": lambda url, bearer: generic_actions.connect_remote(url,bearer), "appearance": "suggested"} _("Connect"): {"callback": lambda url, bearer: generic_actions.connect_remote(url,bearer), "appearance": "suggested"}
} }
entries = [ entries = [
@@ -740,6 +741,36 @@ Generate a title following these rules:
self.selected_chat_row = self.chat_list_box.get_selected_row() self.selected_chat_row = self.chat_list_box.get_selected_row()
self.chat_actions(action, user_data) self.chat_actions(action, user_data)
def youtube_detected(self, video_url):
try:
tries=0
while True:
try:
yt = YouTube(video_url)
video_title = yt.title
break
except Exception as e:
tries+=1
if tries == 4:
raise Exception(e)
transcriptions = generic_actions.get_youtube_transcripts(yt.video_id)
if len(transcriptions) == 0:
self.show_toast(_("This video does not have any transcriptions"), self.main_overlay)
return
if not any(filter(lambda x: '(en' in x and 'auto-generated' not in x and len(transcriptions) > 1, transcriptions)):
transcriptions.insert(1, 'English (translate:en)')
dialog_widget.simple_dropdown(
_('Attach YouTube Video?'),
_('{}\n\nPlease select a transcript to include').format(video_title),
lambda caption_name, yt=yt, video_url=video_url: generic_actions.attach_youtube(yt.title, yt.author, yt.watch_url, video_url, yt.video_id, caption_name),
transcriptions
)
except Exception as e:
logger.error(e)
self.show_toast(_("Error attaching video, please try again"), self.main_overlay)
def cb_text_received(self, clipboard, result): def cb_text_received(self, clipboard, result):
try: try:
text = clipboard.read_text_finish(result) text = clipboard.read_text_finish(result)
@@ -755,22 +786,7 @@ Generate a title following these rules:
r'(?:/[^\\s]*)?' r'(?:/[^\\s]*)?'
) )
if youtube_regex.match(text): if youtube_regex.match(text):
try: self.youtube_detected(text)
yt = YouTube(text)
captions = yt.captions
if len(captions) == 0:
self.show_toast(_("This video does not have any transcriptions"), self.main_overlay)
return
video_title = yt.title
dialog_widget.simple_dropdown(
_('Attach YouTube Video?'),
_('{}\n\nPlease select a transcript to include').format(video_title),
lambda caption_name, video_url=text: generic_actions.attach_youtube(video_url, caption_name),
["{} ({})".format(caption.name.title(), caption.code) for caption in captions]
)
except Exception as e:
logger.error(e)
self.show_toast(_("This video is not available"), self.main_overlay)
elif url_regex.match(text): elif url_regex.match(text):
dialog_widget.simple( dialog_widget.simple(
_('Attach Website? (Experimental)'), _('Attach Website? (Experimental)'),
@@ -829,7 +845,7 @@ Generate a title following these rules:
[element.set_sensitive(True) for element in sensitive_elements] [element.set_sensitive(True) for element in sensitive_elements]
self.get_application().lookup_action('manage_models').set_enabled(True) self.get_application().lookup_action('manage_models').set_enabled(True)
self.title_stack.set_visible_child_name('model_selector') self.title_stack.set_visible_child_name('model_selector' if len(self.model_manager.get_model_list()) > 0 else 'no_models')
if state: if state:
options = { options = {

View File

@@ -6,7 +6,7 @@
<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">400</property> <property name="width-request">400</property>
<property name="height-request">400</property> <property name="height-request">600</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>
<property name="title">Alpaca</property> <property name="title">Alpaca</property>
@@ -97,6 +97,18 @@
</property> </property>
</object> </object>
</child> </child>
<child>
<object class="GtkStackPage">
<property name="name">no_models</property>
<property name="child">
<object class="GtkButton">
<property name="label" translatable="yes">Manage Models</property>
<property name="tooltip-text" translatable="yes">Manage Models</property>
<property name="action-name">app.manage_models</property>
</object>
</property>
</object>
</child>
</object> </object>
</child> </child>
<child type="end"> <child type="end">
@@ -212,6 +224,7 @@
<signal name="paste-clipboard" handler="on_clipboard_paste"/> <signal name="paste-clipboard" handler="on_clipboard_paste"/>
<style> <style>
<class name="message_text_view"/> <class name="message_text_view"/>
<class name="undershoot-bottom"/>
</style> </style>
<property name="wrap-mode">word</property> <property name="wrap-mode">word</property>
<property name="top-margin">10</property> <property name="top-margin">10</property>
@@ -457,6 +470,21 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkLabel" id="ollama_information_label">
<property name="wrap">true</property>
<property name="use-markup">true</property>
<property name="label" translatable="yes">Integrated Ollama instance is not running</property>
<property name="justify">2</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
@@ -639,20 +667,48 @@
</child> </child>
<child> <child>
<object class="AdwNavigationPage"> <object class="AdwNavigationPage">
<property name="title" translatable="yes">Create Model</property> <property name="title" translatable="yes">Model Details</property>
<property name="tag">model_create_page</property> <property name="tag">model_information</property>
<property name="child"> <property name="child">
<object class="AdwToolbarView"> <object class="AdwToolbarView">
<child type="top"> <child type="top">
<object class="AdwHeaderBar"> <object class="AdwHeaderBar">
<child type="start"> <child type="start">
<object class="GtkButton"> <object class="GtkButton" id="model_detail_create_button">
<signal name="clicked" handler="link_button_handler"/> <signal name="clicked" handler="model_detail_create_button_clicked"/>
<property name="icon-name">globe-symbolic</property> <property name="icon-name">edit-copy-symbolic</property>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="vexpand">true</property>
<property name="hexpand">true</property>
<child>
<object class="AdwStatusPage" id="model_detail_page">
<property name="icon-name">brain-augemnted-symbolic</property>
<property name="description">text</property>
<style>
<class name="compact"/>
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</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>
<property name="content"> <property name="content">
<object class="GtkScrolledWindow"> <object class="GtkScrolledWindow">
<property name="vexpand">true</property> <property name="vexpand">true</property>
@@ -726,6 +782,9 @@
<object class="GtkScrolledWindow"> <object class="GtkScrolledWindow">
<property name="margin-start">10</property> <property name="margin-start">10</property>
<property name="margin-end">10</property> <property name="margin-end">10</property>
<style>
<class name="undershoot-bottom"/>
</style>
<child> <child>
<object class="GtkTextView" id="create_model_modelfile"> <object class="GtkTextView" id="create_model_modelfile">
<style> <style>
@@ -825,14 +884,12 @@
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<child> <child>
<object class="GtkTextView" id="file_preview_text_view"> <object class="GtkLabel" id="file_preview_text_label">
<property name="margin-top">12</property> <property name="margin-top">12</property>
<property name="margin-bottom">12</property> <property name="margin-bottom">12</property>
<property name="margin-start">12</property> <property name="margin-start">12</property>
<property name="margin-end">12</property> <property name="margin-end">12</property>
<property name="hexpand">true</property> <property name="selectable">true</property>
<property name="vexpand">true</property>
<property name="editable">false</property>
</object> </object>
</child> </child>
<child> <child>
@@ -1068,10 +1125,16 @@
<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">&lt;ctrl&gt;W</property> <property name="accelerator">&lt;ctrl&gt;Q</property>
<property name="title" translatable="yes">Close application</property> <property name="title" translatable="yes">Close application</property>
</object> </object>
</child> </child>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;ctrl&gt;W</property>
<property name="title" translatable="yes">Delete current chat</property>
</object>
</child>
<child> <child>
<object class="GtkShortcutsShortcut"> <object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;ctrl&gt;I</property> <property name="accelerator">&lt;ctrl&gt;I</property>