17 Commits
1.0.2 ... 1.0.3

Author SHA1 Message Date
jeffser
55a636f4d1 typo 2024-08-01 01:30:20 -06:00
jeffser
0fc8730272 dialog added to version notes 2024-08-01 01:27:49 -06:00
jeffser
61a2bc466e Changed content rating because of the donation dialog 2024-08-01 01:13:14 -06:00
jeffser
62b1923bf4 Added support dialog 2024-08-01 01:11:27 -06:00
jeffser
8e25376a12 Merge branch 'main' of github.com-jeffser:Jeffser/Alpaca 2024-08-01 00:49:26 -06:00
jeffser
a9ab5d45a4 Preparing for 1.0.3 2024-08-01 00:49:19 -06:00
Jeffry Samuel
ce2a2f0b93 :) 2024-08-01 00:40:45 -06:00
jeffser
9cb6b0b665 Code block and markup fixes 2024-08-01 00:33:26 -06:00
jeffser
dfc21fc0e9 Fixed date format for Chinese translation 2024-07-31 23:32:49 -06:00
jeffser
19b089e6c6 Fixed problems with localization 2024-07-31 22:36:39 -06:00
jeffser
02aa2734e0 Changed window min height 2024-07-31 22:08:48 -06:00
jeffser
66f9fd7231 better check for connection 2024-07-31 21:17:31 -06:00
jeffser
1b125cb704 Appearance changes 2024-07-31 21:13:23 -06:00
jeffser
29f5d85c7b Added string to translation list 2024-07-31 19:55:30 -06:00
jeffser
c192a1f31c Added compatibility with generic code blocks without explicit language 2024-07-31 19:54:47 -06:00
jeffser
3b20daf807 Added bearer token entry to connection error dialog 2024-07-31 19:42:46 -06:00
jeffser
760c00e8ae Added German template 2024-07-30 12:48:20 -06:00
8 changed files with 1823 additions and 37 deletions

View File

@@ -22,7 +22,7 @@ Alpaca is an [Ollama](https://github.com/ollama/ollama) client where you can man
- Import / Export chats
- Delete / Edit messages
- YouTube recognition (Ask questions about a YouTube video using the transcript)
- Website recognition (Ask questions about a certain question by parsing the url)
- Website recognition (Ask questions about a certain website by parsing the url)
## Screenies
Chatting with a model | Image recognition | Code highlighting

View File

@@ -70,7 +70,9 @@
<caption>Multiple models being downloaded</caption>
</screenshot>
</screenshots>
<content_rating type="oars-1.1" />
<content_rating type="oars-1.1">
<content_attribute id="money-purchasing">mild</content_attribute>
</content_rating>
<url type="bugtracker">https://github.com/Jeffser/Alpaca/issues</url>
<url type="homepage">https://jeffser.com/alpaca/</url>
<url type="donation">https://github.com/sponsors/Jeffser</url>
@@ -78,6 +80,25 @@
<url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
<releases>
<release version="1.0.3" date="2024-08-01">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.3</url>
<description>
<p>New</p>
<ul>
<li>Bearer Token entry on connection error dialog</li>
<li>Small appearance changes</li>
<li>Compatibility with code blocks without explicit language</li>
<li>Rare, optional and dismissible support dialog</li>
</ul>
<p>Fixes</p>
<ul>
<li>Date format for Simplified Chinese translation</li>
<li>Bug with unsupported localizations</li>
<li>Min height being too large to be used on mobile</li>
<li>Remote connection checker bug</li>
</ul>
</description>
</release>
<release version="1.0.2" date="2024-07-29">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.2</url>
<description>

View File

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

1697
po/de.po Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -172,26 +172,38 @@ def remove_attached_file(self, name):
# RECONNECT REMOTE | WORKS
def reconnect_remote_response(self, dialog, task, entry):
def reconnect_remote_response(self, dialog, task, url_entry, bearer_entry):
response = dialog.choose_finish(task)
if not task or response == "remote":
self.connect_remote(entry.get_text())
self.connect_remote(url_entry.get_text(), bearer_entry.get_text())
elif response == "local":
self.connect_local()
elif response == "close":
self.destroy()
def reconnect_remote(self, current_url):
entry = Gtk.Entry(
def reconnect_remote(self, current_url, current_bearer_token):
entry_url = Gtk.Entry(
css_classes = ["error"],
text = current_url
text = current_url,
placeholder_text = "URL"
)
entry_bearer_token = Gtk.Entry(
css_classes = ["error"] if current_bearer_token else None,
text = current_bearer_token,
placeholder_text = "Bearer Token (Optional)"
)
container = Gtk.Box(
orientation = 1,
spacing = 10
)
container.append(entry_url)
container.append(entry_bearer_token)
dialog = Adw.AlertDialog(
heading=_("Connection Error"),
body=_("The remote instance has disconnected"),
extra_child=entry
extra_child=container
)
entry.connect("activate", lambda entry, dialog: reconnect_remote_response(self, dialog, None, entry))
#entry.connect("activate", lambda entry, dialog: reconnect_remote_response(self, dialog, None, entry))
dialog.add_response("close", _("Close Alpaca"))
dialog.add_response("local", _("Use local instance"))
dialog.add_response("remote", _("Connect"))
@@ -199,7 +211,7 @@ def reconnect_remote(self, current_url):
dialog.choose(
parent = self,
cancellable = None,
callback = lambda dialog, task, entry=entry: reconnect_remote_response(self, dialog, task, entry)
callback = lambda dialog, task, url_entry=entry_url, bearer_entry=entry_bearer_token: reconnect_remote_response(self, dialog, task, url_entry, bearer_entry)
)
# CREATE MODEL | WORKS
@@ -357,3 +369,31 @@ def attach_website(self, url):
cancellable = None,
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)
)

View File

@@ -12,3 +12,7 @@
border-radius: 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);
}

View File

@@ -21,7 +21,7 @@ import gi
gi.require_version('GtkSource', '5')
gi.require_version('GdkPixbuf', '2.0')
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
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 PIL import Image
@@ -44,7 +44,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale')
locale.setlocale(locale.LC_ALL, '')
gettext.bindtextdomain('com.jeffser.Alpaca', localedir)
gettext.textdomain('com.jeffser.Alpaca')
_ = gettext.gettext
@@ -61,6 +60,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
pulling_models = {}
chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": "New Chat", "order": []}
attachments = {}
show_support = True
#Override elements
override_HSA_OVERRIDE_GFX_VERSION = Gtk.Template.Child()
@@ -167,7 +167,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
buffer = self.editing_message["text_view"].get_buffer()
text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False).rstrip('\n')
footer = "<small>" + self.editing_message["footer"] + "</small>"
buffer.insert_markup(buffer.get_end_iter(), footer, len(footer))
buffer.insert_markup(buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
self.chats["chats"][self.chats["selected_chat"]]["messages"][self.editing_message["id"]]["content"] = text
self.editing_message = None
self.save_history()
@@ -229,7 +229,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
#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)
formated_date = 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.message_text_view.get_buffer().set_text("", 0)
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
@@ -290,6 +290,9 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback()
def change_remote_url(self, entry):
if not entry.get_text().startswith("http"):
entry.set_text("http://{}".format(entry.get_text()))
return
self.remote_url = entry.get_text()
logger.debug(f"Changing remote url: {self.remote_url}")
if self.run_remote:
@@ -596,7 +599,7 @@ Generate a title following these rules:
)
message_buffer = message_text.get_buffer()
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))
if footer is not None: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
delete_button = Gtk.Button(
icon_name = "user-trash-symbolic",
@@ -778,14 +781,18 @@ Generate a title following these rules:
def save_server_config(self):
with open(os.path.join(self.config_dir, "server.json"), "w+") 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}, 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, 'show_support': self.show_support}, f, indent=6)
def verify_connection(self):
response = connection_handler.simple_get(f"{connection_handler.url}/api/tags")
if response.status_code == 200:
self.save_server_config()
self.update_list_local_models()
return response.status_code == 200
try:
response = connection_handler.simple_get(f"{connection_handler.url}/api/tags")
if response.status_code == 200:
self.save_server_config()
self.update_list_local_models()
return response.status_code == 200
except Exception as e:
logger.error(e)
return False
def add_code_blocks(self):
text = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), True)
@@ -803,13 +810,23 @@ Generate a title following these rules:
code_text = match.group(2)
parts.append({"type": "code", "text": code_text, "language": language})
pos = end
# Match code blocks without language
no_lang_code_block_pattern = re.compile(r'`\n(.*?)\n`', re.DOTALL)
for match in no_lang_code_block_pattern.finditer(text):
start, end = match.span()
if pos < start:
normal_text = text[pos:start]
parts.append({"type": "normal", "text": normal_text.strip()})
code_text = match.group(1)
parts.append({"type": "code", "text": code_text, "language": None})
pos = end
# Extract any remaining normal text after the last code block
if pos < len(text):
normal_text = text[pos:]
if normal_text.strip():
parts.append({"type": "normal", "text": normal_text.strip()})
bold_pattern = re.compile(r'\*\*(.*?)\*\*') #"**text**"
code_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
@@ -843,17 +860,19 @@ Generate a title following these rules:
start, end = match.span()
if position < start:
message_buffer.insert(message_buffer.get_end_iter(), part['text'][position:start])
message_buffer.insert_markup(message_buffer.get_end_iter(), match.group(0), len(match.group(0)))
message_buffer.insert_markup(message_buffer.get_end_iter(), match.group(0), len(match.group(0).encode('utf-8')))
position = end
if position < len(part['text']):
message_buffer.insert(message_buffer.get_end_iter(), part['text'][position:])
if footer: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer))
if footer: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
self.bot_message_box.append(message_text)
else:
language = GtkSource.LanguageManager.get_default().get_language(part['language'])
language = None
if part['language']:
language = GtkSource.LanguageManager.get_default().get_language(part['language'])
if language:
buffer = GtkSource.Buffer.new_with_language(language)
else:
@@ -871,7 +890,7 @@ Generate a title following these rules:
source_view.set_editable(False)
code_block_box = Gtk.Box(css_classes=["card"], orientation=1, overflow=1)
title_box = Gtk.Box(margin_start=12, margin_top=3, margin_bottom=3, margin_end=3)
title_box.append(Gtk.Label(label=language.get_name() if language else part['language'], hexpand=True, xalign=0))
title_box.append(Gtk.Label(label=language.get_name() if language else _("Code Block"), hexpand=True, xalign=0))
copy_button = Gtk.Button(icon_name="edit-copy-symbolic", css_classes=["flat", "circular"], tooltip_text=_("Copy Message"))
copy_button.connect("clicked", self.on_copy_code_clicked, buffer)
title_box.append(copy_button)
@@ -917,9 +936,9 @@ Generate a title following these rules:
if 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())
if data['done']:
formated_date = self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["date"], '%Y/%m/%d %H:%M:%S'))
text = f"\n\n<small>{data['model'].split(':')[0].replace('-', ' ').title()} ({data['model'].split(':')[1]})\n{formated_date}</small>"
GLib.idle_add(self.bot_message.insert_markup, self.bot_message.get_end_iter(), text, len(text))
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')))
text = f"\n\n{data['model'].split(':')[0].replace('-', ' ').title()} ({data['model'].split(':')[1]})\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')))
self.save_history()
GLib.idle_add(self.bot_message_button_container.set_visible, True)
#Notification
@@ -1082,11 +1101,11 @@ Generate a title following these rules:
for widget in list(self.chat_container): self.chat_container.remove(widget)
for key, message in self.chats['chats'][self.chats["selected_chat"]]['messages'].items():
if message:
formated_date = 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':
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)
else:
self.show_message(message['content'], True, f"\n\n<small>{message['model'].split(':')[0].replace('-', ' ').title()} ({message['model'].split(':')[1]})\n{formated_date}</small>", id=key)
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.add_code_blocks()
self.bot_message = None
@@ -1234,9 +1253,10 @@ Generate a title following these rules:
logger.debug("Showing preferences dialog")
self.preferences_dialog.present(self)
def connect_remote(self, url):
def connect_remote(self, url, bearer_token):
logger.debug(f"Connecting to remote: {url}")
connection_handler.url = url
connection_handler.bearer_token = bearer_token
self.remote_url = connection_handler.url
self.remote_connection_entry.set_text(self.remote_url)
if self.verify_connection() == False: self.connection_error()
@@ -1253,7 +1273,7 @@ Generate a title following these rules:
def connection_error(self):
logger.error("Connection error")
if self.run_remote:
dialogs.reconnect_remote(self, connection_handler.url)
dialogs.reconnect_remote(self, connection_handler.url, connection_handler.bearer_token)
else:
local_instance.reset()
self.show_toast(_("There was an error with the local Ollama instance, so it has been reset"), self.main_overlay)
@@ -1558,7 +1578,11 @@ Generate a title following these rules:
if override in local_instance.overrides:
element.set_text(local_instance.overrides[override])
#Support dialog
if 'show_support' not in data or data['show_support']:
if random.randint(0, 99) == 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.set_hide_on_close(self.run_on_background)
self.remote_connection_entry.set_text(self.remote_url)

View File

@@ -6,7 +6,7 @@
<signal name="close-request" handler="closing_app"/>
<property name="resizable">True</property>
<property name="width-request">360</property>
<property name="height-request">700</property>
<property name="height-request">400</property>
<property name="default-width">1300</property>
<property name="default-height">800</property>
<property name="title">Alpaca</property>
@@ -244,7 +244,7 @@
<property name="valign">3</property>
<property name="tooltip-text" translatable="yes">Send Message</property>
<style>
<class name="suggested-action"/>
<class name="accent"/>
<class name="circular"/>
</style>
<child>