Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7b6e6bbce | ||
|
|
801c10fb77 | ||
|
|
50520b8474 | ||
|
|
b66e2102d3 | ||
|
|
8c0f1fd4d5 | ||
|
|
8b851d1b56 | ||
|
|
f36d6e1b29 | ||
|
|
eecac162ef | ||
|
|
82e7a3a9e1 | ||
|
|
f0505a0242 | ||
|
|
11dd13b430 | ||
|
|
b8fe222052 | ||
|
|
47d19a58aa | ||
|
|
fd67afbf33 | ||
|
|
d06e08a64e | ||
|
|
77b08d9e52 | ||
|
|
9451bf88d0 | ||
|
|
82bb50d663 | ||
|
|
edc3053774 | ||
|
|
1320ddb7d4 | ||
|
|
d95f06a230 | ||
|
|
938ace91c1 | ||
|
|
175cfad81c | ||
|
|
2f399dbb64 | ||
|
|
27558b85af | ||
|
|
bcc1f3fa65 | ||
|
|
fd92a86c5e | ||
|
|
3b95d369b8 | ||
|
|
a12920d801 | ||
|
|
2dd63df533 | ||
|
|
cea1aa5028 | ||
|
|
54b96d4e3a | ||
|
|
a470136476 | ||
|
|
7d35cb08dd | ||
|
|
1f03f1032e |
2
.github/workflows/flatpak-builder.yml
vendored
2
.github/workflows/flatpak-builder.yml
vendored
@@ -13,6 +13,6 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||
with:
|
||||
bundle: Alpaca.flatpak
|
||||
bundle: com.jeffser.Alpaca.flatpak
|
||||
manifest-path: com.jeffser.Alpaca.json
|
||||
cache-key: flatpak-builder-${{ github.sha }}
|
||||
|
||||
4
CODE_OF_CONDUCT.md
Normal file
4
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Alpaca follows [GNOME's code of conduct](https://conduct.gnome.org/), please make sure to read it before interacting in any way with this repository.
|
||||
To report any misconduct please reach out via private message on
|
||||
- X (formally Twitter): [@jeffrysamuer](https://x.com/jeffrysamuer)
|
||||
- Mastodon: [@jeffser@floss.social](https://floss.social/@jeffser)
|
||||
30
CONTRIBUTING.md
Normal file
30
CONTRIBUTING.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Contributing Rules
|
||||
|
||||
## Translations
|
||||
|
||||
If you want to translate or contribute on existing translations please read [this discussion](https://github.com/Jeffser/Alpaca/discussions/153).
|
||||
|
||||
## Code
|
||||
|
||||
1) Before contributing code make sure there's an open [issue](https://github.com/Jeffser/Alpaca/issues) for that particular problem or feature.
|
||||
2) Ask to contribute on the responses to the issue.
|
||||
3) Wait for [my](https://github.com/Jeffser) approval, I might have already started working on that issue.
|
||||
4) Test your code before submitting a pull request.
|
||||
|
||||
## Q&A
|
||||
|
||||
### Do I need to comment my code?
|
||||
|
||||
There's no need to add comments if the code is easy to read by itself.
|
||||
|
||||
### What if I need help or I don't understand the existing code?
|
||||
|
||||
You can reach out on the issue, I'll try to answer as soon as possible.
|
||||
|
||||
### What IDE should I use?
|
||||
|
||||
I use Gnome Builder but you can use whatever you want.
|
||||
|
||||
### Can I be credited?
|
||||
|
||||
You might be credited on the GitHub repository in the [thanks](https://github.com/Jeffser/Alpaca/blob/main/README.md#thanks) section of the README.
|
||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Packaging
|
||||
|
||||
Alpaca only supports [Flatpak](https://flatpak.org/) packaging officially, any other packaging methods might not behave as expected.
|
||||
|
||||
## Official Versions
|
||||
|
||||
The only ways Alpaca is being distributed officially are:
|
||||
|
||||
- [Alpaca's GitHub Repository Releases Page](https://github.com/Jeffser/Alpaca/releases)
|
||||
- [Flathub](https://flathub.org/apps/com.jeffser.Alpaca)
|
||||
@@ -78,6 +78,28 @@
|
||||
<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.1.1" date="2024-08-12">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.1.1</url>
|
||||
<description>
|
||||
<p>New</p>
|
||||
<ul>
|
||||
<li>New duplicate chat option</li>
|
||||
<li>Changed model selector appearance</li>
|
||||
<li>Message entry is focused on launch and chat change</li>
|
||||
<li>Message is focused when it's being edited</li>
|
||||
<li>Added loading spinner when regenerating a message</li>
|
||||
<li>Added Ollama debugging to 'About Alpaca' dialog</li>
|
||||
<li>Changed YouTube transcription dialog appearance and behavior</li>
|
||||
</ul>
|
||||
<p>Fixes</p>
|
||||
<ul>
|
||||
<li>CTRL+W and CTRL+Q stops local instance before closing the app</li>
|
||||
<li>Changed appearance of 'Open Model Manager' button on welcome screen</li>
|
||||
<li>Fixed message generation not working consistently</li>
|
||||
<li>Fixed message edition not working consistently</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.1.0" date="2024-08-10">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.1.0</url>
|
||||
<description>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('Alpaca', 'c',
|
||||
version: '1.1.0',
|
||||
version: '1.1.1',
|
||||
meson_version: '>= 0.62.0',
|
||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||
)
|
||||
|
||||
820
po/alpaca.pot
820
po/alpaca.pot
File diff suppressed because it is too large
Load Diff
820
po/nb_NO.po
820
po/nb_NO.po
File diff suppressed because it is too large
Load Diff
820
po/pt_BR.po
820
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
820
po/zh_CN.po
820
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
@@ -330,7 +330,7 @@ def youtube_caption_response(self, dialog, task, video_url, caption_drop_down):
|
||||
yt = YouTube(video_url)
|
||||
text = "{}\n{}\n{}\n\n".format(yt.title, yt.author, yt.watch_url)
|
||||
selected_caption = caption_drop_down.get_selected_item().get_string()
|
||||
for event in yt.captions[selected_caption.split(' | ')[1]].json_captions['events']:
|
||||
for event in yt.captions[selected_caption.split('(')[1][:-1]].json_captions['events']:
|
||||
text += "{}\n".format(event['segs'][0]['utf8'].replace('\n', '\\n'))
|
||||
if not os.path.exists(os.path.join(self.cache_dir, 'tmp/youtube')):
|
||||
os.makedirs(os.path.join(self.cache_dir, 'tmp/youtube'))
|
||||
@@ -348,9 +348,9 @@ def youtube_caption(self, video_url):
|
||||
return
|
||||
caption_list = Gtk.StringList()
|
||||
for caption in captions:
|
||||
caption_list.append("{} | {}".format(caption.name, caption.code))
|
||||
caption_list.append("{} ({})".format(caption.name.title(), caption.code))
|
||||
caption_drop_down = Gtk.DropDown(
|
||||
enable_search=True,
|
||||
enable_search=len(captions) > 10,
|
||||
model=caption_list
|
||||
)
|
||||
dialog = Adw.AlertDialog(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
Handles running, stopping and resetting the integrated Ollama instance
|
||||
"""
|
||||
import subprocess
|
||||
import threading
|
||||
import os
|
||||
from time import sleep
|
||||
from logging import getLogger
|
||||
@@ -15,6 +16,14 @@ instance = None
|
||||
port = 11435
|
||||
overrides = {}
|
||||
|
||||
def log_output(pipe):
|
||||
with open(os.path.join(data_dir, 'tmp.log'), 'a') as f:
|
||||
with pipe:
|
||||
for line in iter(pipe.readline, ''):
|
||||
print(line, end='')
|
||||
f.write(line)
|
||||
f.flush()
|
||||
|
||||
def start():
|
||||
if not os.path.isdir(os.path.join(cache_dir, 'tmp/ollama')):
|
||||
os.mkdir(os.path.join(cache_dir, 'tmp/ollama'))
|
||||
@@ -23,7 +32,9 @@ def start():
|
||||
params["OLLAMA_HOST"] = f"127.0.0.1:{port}" # You can't change this directly sorry :3
|
||||
params["HOME"] = data_dir
|
||||
params["TMPDIR"] = os.path.join(cache_dir, 'tmp/ollama')
|
||||
instance = subprocess.Popen(["ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, text=True)
|
||||
instance = subprocess.Popen(["ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
|
||||
threading.Thread(target=log_output, args=(instance.stdout,)).start()
|
||||
threading.Thread(target=log_output, args=(instance.stderr,)).start()
|
||||
logger.info("Starting Alpaca's Ollama instance...")
|
||||
logger.debug(params)
|
||||
sleep(1)
|
||||
|
||||
@@ -53,7 +53,7 @@ class AlpacaApplication(Adw.Application):
|
||||
def __init__(self, version):
|
||||
super().__init__(application_id='com.jeffser.Alpaca',
|
||||
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
|
||||
self.create_action('quit', lambda *_: self.quit(), ['<primary>w', '<primary>q'])
|
||||
self.create_action('quit', lambda *_: self.props.active_window.closing_app(None), ['<primary>w', '<primary>q'])
|
||||
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>comma'])
|
||||
self.create_action('about', self.on_about_action)
|
||||
self.version = version
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
}
|
||||
.manage_models_button {
|
||||
padding: 6px 8px 6px 8px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.model_list_box > * {
|
||||
margin: 0;
|
||||
@@ -24,3 +25,6 @@
|
||||
.user_message:focus, .response_message:focus, .editing_message_textview:focus, .code_block:focus {
|
||||
box-shadow: 0 0 1px 2px mix(@accent_color, @window_bg_color, 0.5);
|
||||
}
|
||||
.model_popover {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
@@ -165,12 +165,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
@Gtk.Template.Callback()
|
||||
def stop_message(self, button=None):
|
||||
if self.loading_spinner:
|
||||
self.chat_container.remove(self.loading_spinner)
|
||||
self.loading_spinner.get_parent().remove(self.loading_spinner)
|
||||
message_id = list(self.chats["chats"][self.chats["selected_chat"]]["messages"])[-1]
|
||||
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{self.convert_model_name(self.chats['chats'][self.chats['selected_chat']]['messages'][message_id]['model'], 0)}\n<small>{formated_date}</small>"
|
||||
self.bot_message.insert_markup(self.bot_message.get_end_iter(), text, len(text.encode('utf-8')))
|
||||
self.add_code_blocks()
|
||||
self.bot_message_button_container.set_visible(True)
|
||||
self.toggle_ui_sensitive(True)
|
||||
self.switch_send_stop_button(True)
|
||||
self.bot_message = None
|
||||
@@ -183,7 +184,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
def send_message(self, button=None):
|
||||
if self.editing_message:
|
||||
self.editing_message["button_container"].set_visible(True)
|
||||
self.editing_message["text_view"].set_css_classes(["flat"])
|
||||
self.editing_message["text_view"].set_css_classes(["flat", "user_message"])
|
||||
self.editing_message["text_view"].set_cursor_visible(False)
|
||||
self.editing_message["text_view"].set_editable(False)
|
||||
buffer = self.editing_message["text_view"].get_buffer()
|
||||
@@ -345,6 +346,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
else:
|
||||
logger.info("Closing app...")
|
||||
local_instance.stop()
|
||||
self.get_application().quit()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def model_spin_changed(self, spin):
|
||||
@@ -590,6 +592,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
text_view.set_cursor_visible(True)
|
||||
|
||||
self.editing_message = {"text_view": text_view, "id": message_id, "button_container": button_container, "footer": footer}
|
||||
if text_view.observe_controllers().get_n_items() == 8:
|
||||
print(text_view.observe_controllers().get_n_items())
|
||||
enter_key_controller = Gtk.EventControllerKey.new()
|
||||
enter_key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
|
||||
text_view.add_controller(enter_key_controller)
|
||||
print(text_view.observe_controllers().get_n_items())
|
||||
self.set_focus(text_view)
|
||||
|
||||
def preview_file(self, file_path, file_type, presend_name):
|
||||
logger.debug(f"Previewing file: {file_path}")
|
||||
@@ -1054,7 +1063,7 @@ Generate a title following these rules:
|
||||
if self.bot_message is None:
|
||||
sys.exit()
|
||||
vadjustment = self.chat_window.get_vadjustment()
|
||||
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():
|
||||
if not self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
|
||||
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
|
||||
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"][message_id]["date"], '%Y/%m/%d %H:%M:%S')))
|
||||
@@ -1066,8 +1075,8 @@ 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]
|
||||
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:
|
||||
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)
|
||||
if self.loading_spinner:
|
||||
GLib.idle_add(self.loading_spinner.get_parent().remove, self.loading_spinner)
|
||||
self.loading_spinner = None
|
||||
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] += data['message']['content']
|
||||
@@ -1083,12 +1092,13 @@ Generate a title following these rules:
|
||||
def run_message(self, messages, model, message_id):
|
||||
logger.debug("Running message")
|
||||
self.bot_message_button_container.set_visible(False)
|
||||
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 message_id not in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
||||
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:
|
||||
@@ -1112,7 +1122,7 @@ Generate a title following these rules:
|
||||
GLib.idle_add(self.switch_send_stop_button, True)
|
||||
GLib.idle_add(self.toggle_ui_sensitive, True)
|
||||
if self.loading_spinner:
|
||||
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
||||
GLib.idle_add(self.loading_spinner.get_parent().remove, self.loading_spinner)
|
||||
self.loading_spinner = None
|
||||
|
||||
def regenerate_message(self, message_id, bot_message_box, bot_message_button_container):
|
||||
@@ -1131,10 +1141,12 @@ Generate a title following these rules:
|
||||
self.bot_message_box = bot_message_box
|
||||
for widget in list(bot_message_box):
|
||||
bot_message_box.remove(widget)
|
||||
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
|
||||
bot_message_box.append(self.loading_spinner)
|
||||
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]
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] = ''
|
||||
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
|
||||
data = {
|
||||
"model": self.get_current_model(1),
|
||||
"messages": history,
|
||||
@@ -1329,7 +1341,7 @@ Generate a title following these rules:
|
||||
button = Gtk.Button(
|
||||
label=_("Open Model Manager"),
|
||||
tooltip_text=_("Open Model Manager"),
|
||||
css_classes=["accent"]
|
||||
css_classes=["accent", "pill"]
|
||||
)
|
||||
button.connect('clicked', lambda *_ : self.manage_models_dialog.present(self))
|
||||
button_container.append(button)
|
||||
@@ -1358,12 +1370,16 @@ Generate a title following these rules:
|
||||
for chat_name in self.chats["chats"].keys():
|
||||
self.chats["order"].append(chat_name)
|
||||
self.model_list_box.select_row(self.model_list_box.get_row_at_index(0))
|
||||
self.chats["chats"] = {key: value for key, value in self.chats["chats"].items() if key in self.chats["order"]}
|
||||
if self.chats["selected_chat"] not in self.chats["order"]:
|
||||
self.chats["selected_chat"] = self.chats["order"][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"]
|
||||
for i, m in enumerate(self.local_models):
|
||||
if m == last_model_used:
|
||||
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.chats = {"chats": {}, "selected_chat": None, "order": []}
|
||||
@@ -1395,6 +1411,7 @@ Generate a title following these rules:
|
||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"] = {}
|
||||
self.save_history()
|
||||
self.load_history_into_chat()
|
||||
|
||||
def delete_chat(self, chat_name):
|
||||
logger.info("Deleting chat")
|
||||
@@ -1409,6 +1426,14 @@ Generate a title following these rules:
|
||||
if self.chats['selected_chat'] == chat_name:
|
||||
self.chat_list_box.select_row(self.chat_list_box.get_row_at_index(0))
|
||||
|
||||
def duplicate_chat(self, chat_name):
|
||||
new_chat_name = self.generate_numbered_name(_("Copy of {}").format(chat_name), self.chats["chats"].keys())
|
||||
self.chats["chats"][new_chat_name] = self.chats["chats"][chat_name]
|
||||
self.chats["order"].insert(0, new_chat_name)
|
||||
self.save_history()
|
||||
self.new_chat_element(new_chat_name, True, False)
|
||||
shutil.copytree(os.path.join(self.data_dir, "chats", chat_name), os.path.join(self.data_dir, "chats", new_chat_name))
|
||||
|
||||
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
||||
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())
|
||||
@@ -1430,6 +1455,7 @@ Generate a title following these rules:
|
||||
self.chats["order"].insert(0, chat_name)
|
||||
self.save_history()
|
||||
self.new_chat_element(chat_name, True, False)
|
||||
self.set_focus(self.message_text_view)
|
||||
|
||||
def stop_pull_model(self, model_name):
|
||||
logger.debug("Stopping model pull")
|
||||
@@ -1453,7 +1479,7 @@ Generate a title following these rules:
|
||||
menu_model=self.chat_right_click_menu,
|
||||
has_arrow=False,
|
||||
halign=1,
|
||||
height_request=125
|
||||
height_request=155
|
||||
)
|
||||
self.selected_chat_row = chat_row
|
||||
position = Gdk.Rectangle()
|
||||
@@ -1613,6 +1639,7 @@ Generate a title following these rules:
|
||||
for chat_name, chat_content in data.items():
|
||||
new_chat_name = self.generate_numbered_name(chat_name, list(self.chats['chats'].keys()))
|
||||
self.chats['chats'][new_chat_name] = chat_content
|
||||
self.chats["order"].insert(0, new_chat_name)
|
||||
src_path = os.path.join(temp_dir, chat_name)
|
||||
if os.path.exists(src_path) and os.path.isdir(src_path):
|
||||
dest_path = os.path.join(self.data_dir, "chats", new_chat_name)
|
||||
@@ -1710,6 +1737,8 @@ Generate a title following these rules:
|
||||
action_name = action.get_name()
|
||||
if action_name in ('delete_chat', 'delete_current_chat'):
|
||||
dialogs.delete_chat(self, chat_name)
|
||||
elif action_name in ('duplicate_chat', 'duplicate_current_chat'):
|
||||
self.duplicate_chat(chat_name)
|
||||
elif action_name in ('rename_chat', 'rename_current_chat'):
|
||||
dialogs.rename_chat(self, chat_name, chat_row.get_child())
|
||||
elif action_name in ('export_chat', 'export_current_chat'):
|
||||
@@ -1778,9 +1807,9 @@ Generate a title following these rules:
|
||||
self.available_models = json.load(f)
|
||||
if not os.path.exists(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 and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
|
||||
self.message_text_view.add_controller(key_controller)
|
||||
enter_key_controller = Gtk.EventControllerKey.new()
|
||||
enter_key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
|
||||
self.message_text_view.add_controller(enter_key_controller)
|
||||
self.set_help_overlay(self.shortcut_window)
|
||||
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
|
||||
self.get_application().create_action('new_chat', lambda *_: self.new_chat(), ['<primary>n'])
|
||||
@@ -1789,6 +1818,8 @@ Generate a title following these rules:
|
||||
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_name', lambda *_: dialogs.create_model_from_name(self))
|
||||
self.get_application().create_action('duplicate_chat', self.chat_actions)
|
||||
self.get_application().create_action('duplicate_current_chat', self.current_chat_actions)
|
||||
self.get_application().create_action('delete_chat', self.chat_actions)
|
||||
self.get_application().create_action('delete_current_chat', self.current_chat_actions)
|
||||
self.get_application().create_action('rename_chat', self.chat_actions)
|
||||
|
||||
@@ -100,6 +100,9 @@
|
||||
</style>
|
||||
<property name="popover">
|
||||
<object class="GtkPopover" id="model_popover">
|
||||
<style>
|
||||
<class name="model_popover"/>
|
||||
</style>
|
||||
<property name="has-arrow">false</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
@@ -992,6 +995,10 @@
|
||||
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
||||
<attribute name="action">app.rename_current_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Duplicate Chat</attribute>
|
||||
<attribute name="action">app.duplicate_current_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export Chat</attribute>
|
||||
<attribute name="action">app.export_current_chat</attribute>
|
||||
@@ -1014,6 +1021,10 @@
|
||||
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
||||
<attribute name="action">app.rename_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Duplicate Chat</attribute>
|
||||
<attribute name="action">app.duplicate_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export Chat</attribute>
|
||||
<attribute name="action">app.export_chat</attribute>
|
||||
|
||||
Reference in New Issue
Block a user