Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f042a906d | ||
|
|
ff5e663185 | ||
|
|
9d0daea052 | ||
|
|
31a10ee7d6 | ||
|
|
38abd208ff | ||
|
|
c34713eff5 | ||
|
|
d55aaf19a2 | ||
|
|
9f4b6faf28 | ||
|
|
57184f0a5c | ||
|
|
df568c7217 | ||
|
|
ed440d8935 | ||
|
|
70c183c71d | ||
|
|
b44d457bc5 | ||
|
|
15e4bbb62f | ||
|
|
5d95ba1c15 |
@@ -78,6 +78,21 @@
|
|||||||
<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.5.1" date="2024-10-09">
|
||||||
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/2.5.1</url>
|
||||||
|
<description>
|
||||||
|
<p>New</p>
|
||||||
|
<ul>
|
||||||
|
<li>Added 'Cancel' and 'Save' buttons when editing a message</li>
|
||||||
|
</ul>
|
||||||
|
<p>Fixes</p>
|
||||||
|
<ul>
|
||||||
|
<li>Better handling of image recognition</li>
|
||||||
|
<li>Remove unused files when canceling a model download</li>
|
||||||
|
<li>Better message blocks rendering</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="2.5.0" date="2024-10-06">
|
<release version="2.5.0" date="2024-10-06">
|
||||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/2.5.0</url>
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/2.5.0</url>
|
||||||
<description>
|
<description>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('Alpaca', 'c',
|
project('Alpaca', 'c',
|
||||||
version: '2.5.0',
|
version: '2.5.1',
|
||||||
meson_version: '>= 0.62.0',
|
meson_version: '>= 0.62.0',
|
||||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||||
)
|
)
|
||||||
|
|||||||
786
po/alpaca.pot
786
po/alpaca.pot
File diff suppressed because it is too large
Load Diff
786
po/nb_NO.po
786
po/nb_NO.po
File diff suppressed because it is too large
Load Diff
832
po/pt_BR.po
832
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
786
po/zh_Hans.po
786
po/zh_Hans.po
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,7 @@
|
|||||||
<file alias="icons/scalable/status/down-symbolic.svg">icons/down-symbolic.svg</file>
|
<file alias="icons/scalable/status/down-symbolic.svg">icons/down-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/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 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>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
window = None
|
window = None
|
||||||
|
|
||||||
class edit_text_block(Gtk.TextView):
|
class edit_text_block(Gtk.Box):
|
||||||
__gtype_name__ = 'AlpacaEditTextBlock'
|
__gtype_name__ = 'AlpacaEditTextBlock'
|
||||||
|
|
||||||
def __init__(self, text:str):
|
def __init__(self, text:str):
|
||||||
@@ -27,21 +27,71 @@ class edit_text_block(Gtk.TextView):
|
|||||||
margin_bottom=5,
|
margin_bottom=5,
|
||||||
margin_start=5,
|
margin_start=5,
|
||||||
margin_end=5,
|
margin_end=5,
|
||||||
css_classes=["view", "editing_message_textview"]
|
|
||||||
)
|
|
||||||
self.get_buffer().insert(self.get_buffer().get_start_iter(), text, len(text.encode('utf-8')))
|
|
||||||
enter_key_controller = Gtk.EventControllerKey.new()
|
|
||||||
enter_key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.edit_message() if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
|
|
||||||
self.add_controller(enter_key_controller)
|
|
||||||
|
|
||||||
def edit_message(self):
|
spacing=5,
|
||||||
self.get_parent().get_parent().action_buttons.set_visible(True)
|
orientation=1
|
||||||
self.get_parent().get_parent().set_text(self.get_buffer().get_text(self.get_buffer().get_start_iter(), self.get_buffer().get_end_iter(), False))
|
)
|
||||||
self.get_parent().get_parent().add_footer(self.get_parent().get_parent().dt)
|
self.text_view = Gtk.TextView(
|
||||||
window.save_history(self.get_parent().get_parent().get_parent().get_parent().get_parent().get_parent())
|
halign=0,
|
||||||
|
hexpand=True,
|
||||||
|
css_classes=["view", "editing_message_textview"],
|
||||||
|
wrap_mode=3
|
||||||
|
)
|
||||||
|
cancel_button = Gtk.Button(
|
||||||
|
vexpand=False,
|
||||||
|
valign=2,
|
||||||
|
halign=2,
|
||||||
|
tooltip_text=_("Cancel"),
|
||||||
|
css_classes=['flat', 'circular'],
|
||||||
|
icon_name='cross-large-symbolic'
|
||||||
|
)
|
||||||
|
cancel_button.connect('clicked', lambda *_: self.cancel_edit())
|
||||||
|
save_button = Gtk.Button(
|
||||||
|
vexpand=False,
|
||||||
|
valign=2,
|
||||||
|
halign=2,
|
||||||
|
tooltip_text=_("Save Message"),
|
||||||
|
css_classes=['flat', 'circular'],
|
||||||
|
icon_name='paper-plane-symbolic'
|
||||||
|
)
|
||||||
|
save_button.connect('clicked', lambda *_: self.edit_message())
|
||||||
|
self.append(self.text_view)
|
||||||
|
|
||||||
|
button_container = Gtk.Box(
|
||||||
|
halign=2,
|
||||||
|
spacing=5
|
||||||
|
)
|
||||||
|
button_container.append(cancel_button)
|
||||||
|
button_container.append(save_button)
|
||||||
|
self.append(button_container)
|
||||||
|
self.text_view.get_buffer().insert(self.text_view.get_buffer().get_start_iter(), text, len(text.encode('utf-8')))
|
||||||
|
key_controller = Gtk.EventControllerKey.new()
|
||||||
|
key_controller.connect("key-pressed", self.handle_key)
|
||||||
|
self.text_view.add_controller(key_controller)
|
||||||
|
|
||||||
|
def handle_key(self, controller, keyval, keycode, state):
|
||||||
|
if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK):
|
||||||
|
self.save_edit()
|
||||||
|
return True
|
||||||
|
elif keyval==Gdk.KEY_Escape:
|
||||||
|
self.cancel_edit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def save_edit(self):
|
||||||
|
message_element = self.get_parent().get_parent()
|
||||||
|
message_element.action_buttons.set_visible(True)
|
||||||
|
message_element.set_text(self.text_view.get_buffer().get_text(self.text_view.get_buffer().get_start_iter(), self.text_view.get_buffer().get_end_iter(), False))
|
||||||
|
message_element.add_footer(message_element.dt)
|
||||||
|
window.save_history(message_element.get_parent().get_parent().get_parent().get_parent())
|
||||||
self.get_parent().remove(self)
|
self.get_parent().remove(self)
|
||||||
window.show_toast(_("Message edited successfully"), window.main_overlay)
|
window.show_toast(_("Message edited successfully"), window.main_overlay)
|
||||||
return True
|
|
||||||
|
def cancel_edit(self):
|
||||||
|
message_element = self.get_parent().get_parent()
|
||||||
|
message_element.action_buttons.set_visible(True)
|
||||||
|
message_element.set_text(message_element.text)
|
||||||
|
message_element.add_footer(message_element.dt)
|
||||||
|
self.get_parent().remove(self)
|
||||||
|
|
||||||
class text_block(Gtk.Label):
|
class text_block(Gtk.Label):
|
||||||
__gtype_name__ = 'AlpacaTextBlock'
|
__gtype_name__ = 'AlpacaTextBlock'
|
||||||
@@ -484,17 +534,14 @@ class message(Gtk.Overlay):
|
|||||||
self.content_children = []
|
self.content_children = []
|
||||||
if text:
|
if text:
|
||||||
self.content_children = []
|
self.content_children = []
|
||||||
code_block_pattern = re.compile(r'[```|`](\w*)\n(.*?)\n\s*[```|`]', re.DOTALL)
|
code_block_pattern = re.compile(r'```(\w*)\n(.*?)\n\s*```', re.DOTALL)
|
||||||
|
no_language_code_block_pattern = re.compile(r'`(\w*)\n(.*?)\n\s*`', re.DOTALL)
|
||||||
table_pattern = re.compile(r'((\r?\n){2}|^)([^\r\n]*\|[^\r\n]*(\r?\n)?)+(?=(\r?\n){2}|$)', re.MULTILINE)
|
table_pattern = re.compile(r'((\r?\n){2}|^)([^\r\n]*\|[^\r\n]*(\r?\n)?)+(?=(\r?\n){2}|$)', re.MULTILINE)
|
||||||
bold_pattern = re.compile(r'\*\*(.*?)\*\*') #"**text**"
|
|
||||||
code_pattern = re.compile(r'`([^`\n]*?)`') #"`text`"
|
|
||||||
h1_pattern = re.compile(r'^#\s(.*)$') #"# text"
|
|
||||||
h2_pattern = re.compile(r'^##\s(.*)$') #"## text"
|
|
||||||
markup_pattern = re.compile(r'<(b|u|tt|span.*)>(.*?)<\/(b|u|tt|span)>') #heh butt span, I'm so funny
|
markup_pattern = re.compile(r'<(b|u|tt|span.*)>(.*?)<\/(b|u|tt|span)>') #heh butt span, I'm so funny
|
||||||
parts = []
|
parts = []
|
||||||
pos = 0
|
pos = 0
|
||||||
# Code blocks
|
# Code blocks
|
||||||
for match in code_block_pattern.finditer(self.text):
|
for match in code_block_pattern.finditer(self.text[pos:]):
|
||||||
start, end = match.span()
|
start, end = match.span()
|
||||||
if pos < start:
|
if pos < start:
|
||||||
normal_text = self.text[pos:start]
|
normal_text = self.text[pos:start]
|
||||||
@@ -503,8 +550,17 @@ class message(Gtk.Overlay):
|
|||||||
code_text = match.group(2)
|
code_text = match.group(2)
|
||||||
parts.append({"type": "code", "text": code_text, "language": 'python3' if language == 'python' else language})
|
parts.append({"type": "code", "text": code_text, "language": 'python3' if language == 'python' else language})
|
||||||
pos = end
|
pos = end
|
||||||
|
for match in no_language_code_block_pattern.finditer(self.text[pos:]):
|
||||||
|
start, end = match.span()
|
||||||
|
if pos < start:
|
||||||
|
normal_text = self.text[pos:start]
|
||||||
|
parts.append({"type": "normal", "text": normal_text.strip()})
|
||||||
|
language = match.group(1)
|
||||||
|
code_text = match.group(2)
|
||||||
|
parts.append({"type": "code", "text": code_text, "language": None})
|
||||||
|
pos = end
|
||||||
# Tables
|
# Tables
|
||||||
for match in table_pattern.finditer(self.text):
|
for match in table_pattern.finditer(self.text[pos:]):
|
||||||
start, end = match.span()
|
start, end = match.span()
|
||||||
if pos < start:
|
if pos < start:
|
||||||
normal_text = self.text[pos:start]
|
normal_text = self.text[pos:start]
|
||||||
@@ -513,8 +569,8 @@ class message(Gtk.Overlay):
|
|||||||
parts.append({"type": "table", "text": table_text})
|
parts.append({"type": "table", "text": table_text})
|
||||||
pos = end
|
pos = end
|
||||||
# Text blocks
|
# Text blocks
|
||||||
if pos < len(text):
|
if pos < len(self.text[pos:]):
|
||||||
normal_text = text[pos:]
|
normal_text = self.text[pos:]
|
||||||
if normal_text.strip():
|
if normal_text.strip():
|
||||||
parts.append({"type": "normal", "text": normal_text.strip()})
|
parts.append({"type": "normal", "text": normal_text.strip()})
|
||||||
|
|
||||||
@@ -522,10 +578,12 @@ class message(Gtk.Overlay):
|
|||||||
if part['type'] == 'normal':
|
if part['type'] == 'normal':
|
||||||
text_b = text_block(self.bot)
|
text_b = text_block(self.bot)
|
||||||
part['text'] = part['text'].replace("\n* ", "\n• ")
|
part['text'] = part['text'].replace("\n* ", "\n• ")
|
||||||
part['text'] = code_pattern.sub(r'<tt>\1</tt>', part['text'])
|
part['text'] = re.sub(r'`([^`\n]*?)`', r'<tt>\1</tt>', part['text'])
|
||||||
part['text'] = bold_pattern.sub(r'<b>\1</b>', part['text'])
|
part['text'] = re.sub(r'\*\*(.*?)\*\*', r'<b>\1</b>', part['text'], flags=re.MULTILINE)
|
||||||
part['text'] = h1_pattern.sub(r'<span size="x-large">\1</span>', part['text'])
|
part['text'] = re.sub(r'^#\s+(.*)', r'<span size="x-large">\1</span>', part['text'], flags=re.MULTILINE)
|
||||||
part['text'] = h2_pattern.sub(r'<span size="large">\1</span>', part['text'])
|
part['text'] = re.sub(r'^##\s+(.*)', r'<span size="large">\1</span>', part['text'], flags=re.MULTILINE)
|
||||||
|
part['text'] = re.sub(r'_(\((.*?)\)|\d+)', r'<sub>\2\1</sub>', part['text'], flags=re.MULTILINE)
|
||||||
|
part['text'] = re.sub(r'\^(\((.*?)\)|\d+)', r'<sup>\2\1</sup>', part['text'], flags=re.MULTILINE)
|
||||||
pos = 0
|
pos = 0
|
||||||
for match in markup_pattern.finditer(part['text']):
|
for match in markup_pattern.finditer(part['text']):
|
||||||
start, end = match.span()
|
start, end = match.span()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import gi
|
|||||||
gi.require_version('Gtk', '4.0')
|
gi.require_version('Gtk', '4.0')
|
||||||
gi.require_version('GtkSource', '5')
|
gi.require_version('GtkSource', '5')
|
||||||
from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk
|
from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk
|
||||||
import logging, os, datetime, re, shutil, threading, json, sys
|
import logging, os, datetime, re, shutil, threading, json, sys, glob
|
||||||
from ..internal import config_dir, data_dir, cache_dir, source_dir
|
from ..internal import config_dir, data_dir, cache_dir, source_dir
|
||||||
from .. import available_models_descriptions, dialogs
|
from .. import available_models_descriptions, dialogs
|
||||||
|
|
||||||
@@ -52,6 +52,23 @@ class model_selector_popup(Gtk.Popover):
|
|||||||
child=scroller
|
child=scroller
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class model_selector_row(Gtk.ListBoxRow):
|
||||||
|
__gtype_name__ = 'AlpacaModelSelectorRow'
|
||||||
|
|
||||||
|
def __init__(self, model_name:str, image_recognition:bool):
|
||||||
|
super().__init__(
|
||||||
|
child = Gtk.Label(
|
||||||
|
label=window.convert_model_name(model_name, 0),
|
||||||
|
halign=1,
|
||||||
|
hexpand=True
|
||||||
|
),
|
||||||
|
halign=0,
|
||||||
|
hexpand=True,
|
||||||
|
name=model_name,
|
||||||
|
tooltip_text=window.convert_model_name(model_name, 0)
|
||||||
|
)
|
||||||
|
self.image_recognition = image_recognition
|
||||||
|
|
||||||
class model_selector_button(Gtk.MenuButton):
|
class model_selector_button(Gtk.MenuButton):
|
||||||
__gtype_name__ = 'AlpacaModelSelectorButton'
|
__gtype_name__ = 'AlpacaModelSelectorButton'
|
||||||
|
|
||||||
@@ -91,19 +108,18 @@ class model_selector_button(Gtk.MenuButton):
|
|||||||
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):
|
||||||
model_row = Gtk.ListBoxRow(
|
vision = False
|
||||||
child = Gtk.Label(
|
response = window.ollama_instance.request("POST", "api/show", json.dumps({"name": model_name}))
|
||||||
label=window.convert_model_name(model_name, 0),
|
if response.status_code != 200:
|
||||||
halign=1,
|
logger.error(f"Status code was {response.status_code}")
|
||||||
hexpand=True
|
return
|
||||||
),
|
try:
|
||||||
halign=0,
|
vision = 'projector_info' in json.loads(response.text)
|
||||||
hexpand=True,
|
except Exception as e:
|
||||||
name=model_name,
|
logger.error(f"Error fetching vision info: {str(e)}")
|
||||||
tooltip_text=window.convert_model_name(model_name, 0)
|
model_row = model_selector_row(model_name, vision)
|
||||||
)
|
GLib.idle_add(self.get_popover().model_list_box.append, model_row)
|
||||||
self.get_popover().model_list_box.append(model_row)
|
GLib.idle_add(self.change_model, model_name)
|
||||||
self.change_model(model_name)
|
|
||||||
|
|
||||||
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))
|
||||||
@@ -183,9 +199,22 @@ class pulling_model(Gtk.ListBoxRow):
|
|||||||
name=model_name
|
name=model_name
|
||||||
)
|
)
|
||||||
self.error = None
|
self.error = None
|
||||||
|
self.digests = []
|
||||||
|
|
||||||
def update(self, data):
|
def update(self, data):
|
||||||
|
if 'digest' in data and data['digest'] not in self.digests:
|
||||||
|
self.digests.append(data['digest'].replace(':', '-'))
|
||||||
if not self.get_parent():
|
if not self.get_parent():
|
||||||
|
logger.info("Pulling of '{}' was canceled".format(self.get_name()))
|
||||||
|
directory = os.path.join(data_dir, '.ollama', 'models', 'blobs')
|
||||||
|
for digest in self.digests:
|
||||||
|
files_to_delete = glob.glob(os.path.join(directory, digest + '*'))
|
||||||
|
for file in files_to_delete:
|
||||||
|
logger.info("Deleting '{}'".format(file))
|
||||||
|
try:
|
||||||
|
os.remove(file)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Can't delete file {file}: {e}")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
if 'error' in data:
|
if 'error' in data:
|
||||||
self.error = data['error']
|
self.error = data['error']
|
||||||
@@ -273,7 +302,7 @@ class local_model_list(Gtk.ListBox):
|
|||||||
|
|
||||||
def add_model(self, model_name:str):
|
def add_model(self, model_name:str):
|
||||||
model = local_model(model_name)
|
model = local_model(model_name)
|
||||||
self.append(model)
|
GLib.idle_add(self.append, model)
|
||||||
if not self.get_visible():
|
if not self.get_visible():
|
||||||
self.set_visible(True)
|
self.set_visible(True)
|
||||||
|
|
||||||
@@ -466,7 +495,7 @@ class model_manager_container(Gtk.Box):
|
|||||||
else:
|
else:
|
||||||
self.local_list.set_visible(True)
|
self.local_list.set_visible(True)
|
||||||
for model in data['models']:
|
for model in data['models']:
|
||||||
self.add_local_model(model['name'])
|
threading.Thread(target=self.add_local_model, args=(model['name'], )).start()
|
||||||
else:
|
else:
|
||||||
window.connection_error()
|
window.connection_error()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -484,48 +513,18 @@ class model_manager_container(Gtk.Box):
|
|||||||
def change_model(self, model_name:str):
|
def change_model(self, model_name:str):
|
||||||
self.model_selector.change_model(model_name)
|
self.model_selector.change_model(model_name)
|
||||||
|
|
||||||
def has_vision(self, model_name) -> bool:
|
|
||||||
response = (
|
|
||||||
window.ollama_instance.request(
|
|
||||||
"POST", "api/show", json.dumps({"name": model_name})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
|
||||||
logger.error(f"Status code was {response.status_code}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
model_info = json.loads(response.text)
|
|
||||||
logger.debug(f"Vision for {model_name}: {'projector_info' in model_info}")
|
|
||||||
return 'projector_info' in model_info
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error fetching vision info: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def verify_if_image_can_be_used(self):
|
def verify_if_image_can_be_used(self):
|
||||||
logger.debug("Verifying if image can be used")
|
logger.debug("Verifying if image can be used")
|
||||||
selected = self.get_selected_model()
|
selected = self.model_selector.get_popover().model_list_box.get_selected_row()
|
||||||
if selected == None:
|
if selected and selected.image_recognition:
|
||||||
return False
|
for name, content in window.attachments.items():
|
||||||
|
if content['type'] == 'image':
|
||||||
# first try ollama show API.
|
content['button'].set_css_classes(["flat"])
|
||||||
if self.has_vision(selected):
|
|
||||||
return True
|
return True
|
||||||
|
elif selected:
|
||||||
# then fall back to the old method.)
|
|
||||||
|
|
||||||
selected = selected.split(":")[0]
|
|
||||||
with open(os.path.join(source_dir, 'available_models.json'), 'r', encoding="utf-8") as f:
|
|
||||||
if selected in [key for key, value in json.load(f).items() if value["image"]]:
|
|
||||||
for name, content in window.attachments.items():
|
|
||||||
if content['type'] == 'image':
|
|
||||||
content['button'].set_css_classes(["flat"])
|
|
||||||
return True
|
|
||||||
for name, content in window.attachments.items():
|
for name, content in window.attachments.items():
|
||||||
if content['type'] == 'image':
|
if content['type'] == 'image':
|
||||||
content['button'].set_css_classes(["flat", "error"])
|
content['button'].set_css_classes(["flat", "error"])
|
||||||
return False
|
|
||||||
|
|
||||||
def pull_model(self, model_name:str, modelfile:str=None):
|
def pull_model(self, model_name:str, modelfile:str=None):
|
||||||
if ':' not in model_name:
|
if ':' not in model_name:
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ class terminal(Vte.Terminal):
|
|||||||
|
|
||||||
self.set_pty(pty)
|
self.set_pty(pty)
|
||||||
|
|
||||||
env = {
|
|
||||||
'TERM': "xterm-256color",
|
|
||||||
'SUDO_ASKPASS': "sh -c 'pkexec echo'"
|
|
||||||
}
|
|
||||||
|
|
||||||
pty.spawn_async(
|
pty.spawn_async(
|
||||||
GLib.get_current_dir(),
|
GLib.get_current_dir(),
|
||||||
script,
|
script,
|
||||||
|
|||||||
2
src/icons/cross-large-symbolic.svg
Normal file
2
src/icons/cross-large-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"><path d="m 3 2 c -0.265625 0 -0.519531 0.105469 -0.707031 0.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 l 4.292969 4.292969 l -4.292969 4.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 s 1.023437 0.390625 1.414062 0 l 4.292969 -4.292969 l 4.292969 4.292969 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 s 0.390625 -1.023437 0 -1.414062 l -4.292969 -4.292969 l 4.292969 -4.292969 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 s -0.519531 0.105469 -0.707031 0.292969 l -4.292969 4.292969 l -4.292969 -4.292969 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 0 0" fill="#222222"/></svg>
|
||||||
|
After Width: | Height: | Size: 816 B |
Reference in New Issue
Block a user