add per conversation settings

This commit is contained in:
2026-04-06 12:13:41 +02:00
parent e32f8c2c05
commit c24e2af029
8 changed files with 136 additions and 35 deletions
+1
View File
@@ -5,6 +5,7 @@
<file preprocess="xml-stripblanks" alias="ui/export_dialog.ui">views/export_dialog.ui</file>
<file preprocess="xml-stripblanks" alias="ui/preferences_window.ui">views/preferences_window.ui</file>
<file preprocess="xml-stripblanks" alias="ui/save_dialog.ui">views/save_dialog.ui</file>
<file preprocess="xml-stripblanks" alias="ui/chat_settings_dialog.ui">views/chat_settings_dialog.ui</file>
<file preprocess="xml-stripblanks" alias="ui/thread_item.ui">widgets/thread_item.ui</file>
<file preprocess="xml-stripblanks" alias="ui/item.ui">widgets/item.ui</file>
<file preprocess="xml-stripblanks" alias="ui/model_item.ui">widgets/model_item.ui</file>
+7 -27
View File
@@ -60,35 +60,14 @@ class LLM:
self.load_model()
messages = []
for msg in chat.get("content", []):
role = msg.get("role", "user")
if role == self.app.user_name:
role = "user"
elif role == self.app.bot_name:
role = "assistant"
else:
role = "user"
content = msg.get("content", "")
system_prompt = chat.get("system_prompt", "")
if system_prompt:
messages.append({
"role": role,
"content": [{"type": "text", "text": content}]
"role": "system",
"content": [{"type": "text", "text": system_prompt}]
})
with self.engine.create_conversation(messages=messages) as conv:
response = conv.send_message(prompt)
GLib.idle_add(callback, response["content"][0]["text"])
except Exception as e:
GLib.idle_add(error_callback, str(e))
t = threading.Thread(target=thread_run)
t.start()
def ask_async(self, prompt, chat, callback, error_callback):
def thread_run():
try:
self.load_model()
messages = []
for msg in chat.get("content", []):
role = msg.get("role", "user")
if role == self.app.user_name:
@@ -109,6 +88,7 @@ class LLM:
for item in chunk.get("content", []):
if item.get("type") == "text":
GLib.idle_add(callback, item["text"])
GLib.idle_add(callback, None)
except Exception as e:
GLib.idle_add(error_callback, str(e))
+1
View File
@@ -142,6 +142,7 @@ class BavarderApplication(Adw.Application):
"title": "New Chat " + str(chat_id),
"starred": False,
"content": [],
"system_prompt": "",
}
self.data["chats"].append(chat)
+1
View File
@@ -5,6 +5,7 @@ python = import('python')
blueprints = custom_target('blueprints',
input: files(
'gtk/help-overlay.blp',
'views/chat_settings_dialog.blp',
'views/export_dialog.blp',
'views/preferences_window.blp',
'views/save_dialog.blp',
+36
View File
@@ -0,0 +1,36 @@
using Gtk 4.0;
using Adw 1;
template $ChatSettingsDialog : Adw.MessageDialog {
title: _("Chat Settings");
close-response: "cancel";
Adw.EntryRow title_entry {
title: _("Chat Title");
show-apply-button: true;
}
Gtk.Label {
label: _("System Prompt");
halign: start;
margin-top: 12;
}
Gtk.TextView system_prompt_view {
height-request: 150;
wrap-mode: word;
margin-top: 6;
}
Adw.ButtonContent {
label: _("Cancel");
use-underline: true;
response: "cancel";
}
Adw.ButtonContent {
label: _("Save");
use-underline: true;
response: "save";
}
}
+44
View File
@@ -0,0 +1,44 @@
from gi.repository import Gtk, Adw, GLib
from bavarder.constants import app_id, rootdir
@Gtk.Template(resource_path=f"{rootdir}/ui/chat_settings_dialog.ui")
class ChatSettingsDialog(Adw.MessageDialog):
__gtype_name__ = "ChatSettingsDialog"
title_entry = Gtk.Template.Child()
system_prompt_view = Gtk.Template.Child()
def __init__(self, parent, chat, **kwargs):
super().__init__(**kwargs)
self.parent = parent
self.chat = chat
self.app = parent.app
self.setup()
def setup(self):
self.title_entry.set_text(self.chat.get("title", ""))
system_prompt = self.chat.get("system_prompt", "")
buffer = self.system_prompt_view.get_buffer()
buffer.set_text(system_prompt, -1)
@Gtk.Template.Callback()
def on_save_clicked(self, widget, *args):
new_title = self.title_entry.get_text().strip()
if new_title:
self.chat["title"] = new_title
self.parent.title.set_title(new_title)
buffer = self.system_prompt_view.get_buffer()
system_prompt = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False)
self.chat["system_prompt"] = system_prompt
self.parent.threads_row_activated_cb()
self.close()
@Gtk.Template.Callback()
def on_cancel_clicked(self, widget, *args):
self.close()
+1
View File
@@ -3,6 +3,7 @@ views_dir = join_paths(MODULE_DIR, 'views')
views_sources = [
'__init__.py',
'about_window.py',
'chat_settings_dialog.py',
'export_dialog.py',
'preferences_window.py',
'save_dialog.py',
+45 -8
View File
@@ -31,6 +31,7 @@ from bavarder.widgets.thread_item import ThreadItem
from bavarder.widgets.item import Item
from bavarder.threading import KillableThread
from bavarder.views.export_dialog import ExportDialog
from bavarder.views.chat_settings_dialog import ChatSettingsDialog
class CustomEntry(Gtk.TextView):
def __init__(self, **kwargs):
@@ -84,6 +85,7 @@ class BavarderWindow(Adw.ApplicationWindow):
self.create_action("cancel", self.cancel, ["<primary>Escape"])
self.create_action("clear_all", self.on_clear_all)
self.create_action("export", self.on_export, ["<primary>e"])
self.create_action("chat_settings", self.on_chat_settings, ["<primary>comma"])
self.settings.bind(
"width", self, "default-width", Gio.SettingsBindFlags.DEFAULT
@@ -252,6 +254,12 @@ class BavarderWindow(Adw.ApplicationWindow):
toast.set_title(_("Nothing to export!"))
self.toast_overlay.add_toast(toast)
def on_chat_settings(self, *args):
if self.chat:
dialog = ChatSettingsDialog(self, self.chat)
dialog.set_transient_for(self)
dialog.present()
# MODEL - OFFLINE
def load_model_selector(self):
provider_menu = Gio.Menu()
@@ -333,12 +341,28 @@ class BavarderWindow(Adw.ApplicationWindow):
self.add_user_item(prompt)
def on_response(response):
if not response:
self.add_assistant_item(_("Sorry, I don't know what to say."))
else:
self.add_assistant_item(response)
self.toast.dismiss()
self.response_buffer = ""
self.assistant_item_added = False
def on_token(token):
if token is None:
self.toast.dismiss()
if not self.response_buffer:
self.add_assistant_item(_("Sorry, I don't know what to say."))
else:
self.update_last_assistant_item(self.response_buffer.strip())
return
self.response_buffer += token
min_content = 5
if len(self.response_buffer) >= min_content:
if not self.assistant_item_added:
self.add_assistant_item(self.response_buffer.strip())
self.assistant_item_added = True
else:
self.update_last_assistant_item(self.response_buffer.strip())
self.scroll_down()
def on_error(error):
self.toast.dismiss()
@@ -349,7 +373,7 @@ class BavarderWindow(Adw.ApplicationWindow):
self.toast.set_timeout(0)
self.toast_overlay.add_toast(self.toast)
self.app.ask(prompt, self.chat, on_response, on_error)
self.app.ask(prompt, self.chat, on_token, on_error)
# @Gtk.Template.Callback()
# def on_emoji(self, *args):
@@ -395,12 +419,19 @@ class BavarderWindow(Adw.ApplicationWindow):
self.scroll_down()
def get_model_name(self):
model_path = self.app.data.get("models", {}).get("model_path", "")
if model_path and os.path.exists(model_path):
return os.path.basename(model_path)
return "litert-lm"
def add_assistant_item(self, content):
model_name = self.get_model_name()
c = {
"role": self.app.bot_name,
"content": content,
"time": self.get_time(),
"model": "litert-lm",
"model": model_name,
}
self.content.append(c)
@@ -408,3 +439,9 @@ class BavarderWindow(Adw.ApplicationWindow):
self.threads_row_activated_cb()
self.scroll_down()
def update_last_assistant_item(self, content):
if self.content:
self.content[-1]["content"] = content
self.threads_row_activated_cb()
self.scroll_down()