Bavarder/src/main.py

348 lines
11 KiB
Python
Raw Permalink Normal View History

2023-04-26 18:22:27 +05:30
# main.py
#
2023-07-19 19:29:17 +05:30
# Copyright 2023
2023-04-26 18:22:27 +05:30
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
import sys
import gi
2023-07-19 19:29:17 +05:30
import time
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
gi.require_version('Xdp', '1.0')
gi.require_version('GtkSource', '5')
2024-02-25 05:24:02 +05:30
from gi.repository import Gtk, Gio, Adw, Xdp, GLib
2023-07-19 19:29:17 +05:30
from .views.window import BavarderWindow
from .views.about_window import AboutWindow
from .views.preferences_window import PreferencesWindow
from .constants import app_id
from .providers import PROVIDERS
2023-04-30 20:01:54 +05:30
import json
2023-07-19 19:29:17 +05:30
from gpt4all import GPT4All
import os
2023-04-26 18:22:27 +05:30
2023-07-19 19:29:17 +05:30
user_config_dir = os.environ.get(
"XDG_CONFIG_HOME", os.environ["HOME"] + "/.config"
)
2023-04-26 18:22:27 +05:30
2023-07-19 19:29:17 +05:30
user_data_dir = os.environ.get(
"XDG_DATA_HOME", os.environ["HOME"] + "/.local/share"
)
2023-04-27 04:02:45 +05:30
2023-07-19 19:29:17 +05:30
user_cache_dir = os.environ.get(
"XDG_CACHE_HOME", os.environ["HOME"] + "/.cache"
)
2023-04-27 04:02:45 +05:30
2023-07-19 19:29:17 +05:30
model_path = os.path.join(user_cache_dir, "bavarder", "models")
2023-04-26 18:22:27 +05:30
2023-07-19 19:29:17 +05:30
class BavarderApplication(Adw.Application):
"""The main application singleton class."""
2023-07-19 19:29:17 +05:30
model_name = "ggml-model-gpt4all-falcon-q4_0.bin"
models = set()
model = None
action_running_in_background = False
2023-08-20 15:53:08 +05:30
number_of_win = 0
2023-05-26 00:45:08 +05:30
2023-07-19 19:29:17 +05:30
def __init__(self):
super().__init__(application_id='io.github.Bavarder.Bavarder',
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
self.create_action("quit", self.on_quit, ["<primary>q"])
2023-08-21 04:07:47 +05:30
self.create_action("close", self.on_close, ["<primary>w"])
2023-07-19 19:29:17 +05:30
self.create_action('about', self.on_about_action)
self.create_action('preferences', self.on_preferences_action, ['<primary>comma'])
self.create_action('new_chat', self.on_new_chat_action, ["<primary>n"])
self.create_action('ask', self.on_ask, ["Return"])
2023-08-21 04:07:47 +05:30
self.create_action('new_window', self.on_new_window, ["<primary><shift>n"])
2023-05-26 00:45:08 +05:30
2023-07-19 19:29:17 +05:30
self.data_path = os.path.join(user_data_dir, "bavarder")
2023-07-19 19:29:17 +05:30
if not os.path.exists(self.data_path):
os.makedirs(self.data_path)
2023-07-19 19:29:17 +05:30
if not os.path.exists(model_path):
os.makedirs(model_path)
2023-05-06 22:54:25 +05:30
2023-07-19 19:29:17 +05:30
self.data_path = os.path.join(self.data_path, "data.json")
2023-04-26 18:22:27 +05:30
2023-07-19 19:29:17 +05:30
self.data = {
"chats": [],
2023-08-22 21:10:10 +05:30
"providers": {
"google-flan-t5-xxl": {"enabled": True, "data": {}},
"gpt-2": {"enabled": True, "data": {}},
},
"models": {}
2023-07-19 19:29:17 +05:30
}
2023-07-19 19:29:17 +05:30
if os.path.exists(self.data_path):
try:
with open(self.data_path, "r", encoding="utf-8") as f:
self.data = json.load(f)
except Exception: # if there is an error, we use a plain config
pass
2023-04-27 04:02:45 +05:30
2023-05-26 00:04:43 +05:30
self.settings = Gio.Settings(schema_id=app_id)
2023-04-27 04:02:45 +05:30
2023-07-19 19:29:17 +05:30
self.local_mode = self.settings.get_boolean("local-mode")
self.current_provider = self.settings.get_string("current-provider")
self.model_name = self.settings.get_string("model")
2023-04-26 18:22:27 +05:30
self.create_stateful_action(
"set_provider",
GLib.VariantType.new("s"),
2023-07-19 19:29:17 +05:30
GLib.Variant("s", self.current_provider),
self.on_set_provider_action
)
2023-07-19 19:29:17 +05:30
self.create_stateful_action(
"set_model",
GLib.VariantType.new("s"),
GLib.Variant("s", self.model_name),
self.on_set_model_action
2023-05-14 19:30:39 +05:30
)
2023-08-02 23:49:45 +05:30
self.bot_name = self.settings.get_string("bot-name")
self.user_name = self.settings.get_string("user-name")
2023-07-19 19:29:17 +05:30
def on_set_provider_action(self, action, *args):
2023-07-19 19:29:17 +05:30
self.current_provider = args[0].get_string()
Gio.SimpleAction.set_state(self.lookup_action("set_provider"), args[0])
2023-07-19 19:29:17 +05:30
def on_set_model_action(self, action, *args):
previous = self.model_name
self.model_name = args[0].get_string()
if previous != self.model_name:
# reset model for loading the new one
self.model = None
Gio.SimpleAction.set_state(self.lookup_action("set_model"), args[0])
def save(self):
with open(self.data_path, "w", encoding="utf-8") as f:
self.data = json.dump(self.data, f)
self.settings.set_boolean("local-mode", self.local_mode)
self.settings.set_string("current-provider", self.current_provider)
self.settings.set_string("model", self.model_name)
2023-08-02 23:49:45 +05:30
self.settings.set_string("bot-name", self.bot_name)
self.settings.set_string("user-name", self.user_name)
2023-07-19 19:29:17 +05:30
def on_quit(self, action, *args, **kwargs):
2023-05-06 01:46:17 +05:30
"""Called when the user activates the Quit action."""
2023-08-21 04:07:47 +05:30
self.save()
self.quit()
def on_close(self, action, *args, **kwargs):
2023-08-20 15:53:08 +05:30
if self.number_of_win == 1:
2023-08-21 04:07:47 +05:30
self.on_quit(action, *args, **kwargs)
2023-08-20 15:53:08 +05:30
else:
self.win.destroy()
self.number_of_win -= 1
2023-07-19 19:29:17 +05:30
def on_new_chat_action(self, widget, _):
chat_id = 0
for chat in self.data["chats"]:
if chat["id"] > chat_id:
chat_id = chat["id"]
chat_id += 1
chat = {
"id": chat_id,
2023-08-11 22:09:12 +05:30
"title": "New Chat " + str(chat_id),
2023-07-19 19:29:17 +05:30
"starred": False,
"content": [],
}
self.data["chats"].append(chat)
self.win.load_threads()
2023-05-07 19:28:04 +05:30
2023-04-26 18:22:27 +05:30
def do_activate(self):
"""Called when the application is activated.
We raise the application's main window, creating it if
necessary.
"""
2023-08-20 15:53:08 +05:30
self.new_window()
@property
def win(self):
"""The application's main window."""
return self.props.active_window
def new_window(self, window=None):
if window:
win = self.props.active_window
else:
win = BavarderWindow(application=self)
self.number_of_win += 1
2023-08-21 04:20:34 +05:30
win.connect("close-request", self.on_close)
2023-04-30 20:01:54 +05:30
self.providers = {}
2023-07-19 19:29:17 +05:30
for provider in PROVIDERS:
2023-08-20 15:53:08 +05:30
p = provider(self, win)
2023-05-21 23:42:20 +05:30
2023-07-19 19:29:17 +05:30
self.providers[p.slug] = p
2023-05-06 03:55:57 +05:30
2023-08-20 15:53:08 +05:30
win.load_model_selector()
win.load_provider_selector()
win.present()
2023-08-20 15:53:08 +05:30
def on_new_window(self, widget, _):
self.new_window()
2023-05-01 00:33:34 +05:30
2023-04-26 18:22:27 +05:30
def on_about_action(self, widget, _):
"""Callback for the app.about action."""
2023-07-19 19:29:17 +05:30
about = AboutWindow(self.win)
about.present()
2023-04-26 18:22:27 +05:30
2023-07-19 19:29:17 +05:30
def on_preferences_action(self, widget, _):
2023-04-26 18:22:27 +05:30
"""Callback for the app.preferences action."""
2023-08-22 21:10:10 +05:30
self.preferences_window = PreferencesWindow(self.win)
self.preferences_window.present()
2023-04-27 04:02:45 +05:30
2023-04-29 20:59:45 +05:30
2023-04-26 18:22:27 +05:30
def create_action(self, name, callback, shortcuts=None):
"""Add an application action.
Args:
name: the name of the action
callback: the function to be called when the action is
activated
shortcuts: an optional list of accelerators
"""
action = Gio.SimpleAction.new(name, None)
action.connect("activate", callback)
self.add_action(action)
2023-07-19 19:29:17 +05:30
2023-04-26 18:22:27 +05:30
if shortcuts:
self.set_accels_for_action(f"app.{name}", shortcuts)
def create_stateful_action(self, name, parameter_type, initial_state, callback, shortcuts=None):
"""Add a stateful application action."""
action = Gio.SimpleAction.new_stateful(
name, parameter_type, initial_state)
action.connect("activate", callback)
2023-07-19 19:29:17 +05:30
self.add_action(action)
2023-07-19 19:29:17 +05:30
if shortcuts:
2023-07-19 19:29:17 +05:30
self.set_accels_for_action(f"app.{name}", shortcuts)
2023-07-19 19:29:17 +05:30
def on_ask(self, widget, _):
try:
self.win.on_ask()
except AttributeError:
pass
def ask(self, prompt, chat):
if self.local_mode:
if not self.setup_chat(): # NO MODELS:
2023-08-02 20:40:06 +05:30
return _("Please download a model from Preferences by clicking on the Dot Menu at the top!")
2023-07-19 19:29:17 +05:30
else:
2023-08-20 15:30:57 +05:30
for p in ["Hi", "Hello"]:
if p.lower() in prompt.lower():
return _("Hello, I am Bavarder, a Chit-Chat AI")
2023-08-21 03:57:19 +05:30
system_template = f"""You are a helpful and friendly AI assistant with the name {self.bot_name}. The name of the user are {self.user_name}. Respond very concisely."""
with self.model.chat_session(self.model_settings.get("system_template", system_template)):
2023-07-19 19:29:17 +05:30
self.model.current_chat_session = chat["content"].copy()
response = self.model.generate(
prompt=prompt,
2023-08-20 23:06:13 +05:30
top_k=int(self.model_settings.get("top_k", 40)),
top_p=float(self.model_settings.get("top_p", 0.5)),
temp=float(self.model_settings.get("temperature", 0.9)),
max_tokens=int(self.model_settings.get("max_tokens", 500)),
repeat_penalty=float(self.model_settings.get("repetition_penalty", 1.20)),
repeat_last_n=int(self.model_settings.get("repeat_last_n", 64)),
n_batch=int(self.model_settings.get("n_batch", 10)),
)
2023-07-19 19:29:17 +05:30
else:
l = list(self.providers.values())
for p in l:
if p.enabled and p.slug == self.current_provider:
response = self.providers[self.current_provider].ask(prompt, chat)
break
else:
2023-08-02 20:40:06 +05:30
response = _("Please enable a provider from the Dot Menu")
2023-07-19 19:29:17 +05:30
return response
@property
def model_settings(self):
try:
return self.data["models"][self.model_name]
except KeyError:
try:
self.data["models"][self.model_name] = {}
except KeyError:
self.data["models"] = {}
self.data["models"][self.model_name] = {}
2023-08-20 23:06:13 +05:30
return self.data["models"][self.model_name]
2023-07-19 19:29:17 +05:30
def setup_chat(self):
if not self.models:
self.list_models()
if not self.models:
return False
else:
if self.model is None:
if self.model_name not in self.models:
self.download_model(self.model_name)
self.model = GPT4All(self.model_name, model_path=model_path)
2023-08-11 22:09:12 +05:30
return True
2023-07-19 19:29:17 +05:30
def download_model(self, model=None):
if model:
self.model_name = model
GPT4All.retrieve_model(self.model_name, model_path=model_path, verbose=True)
self.models.add(self.model_name)
def list_models(self):
self.models = set()
for root, dirs, files in os.walk(model_path):
for model in files:
self.models.add(model)
def delete_model(self, model):
os.remove(os.path.join(model_path, model))
self.list_models()
def check_network(self):
return False
2023-04-26 18:22:27 +05:30
2023-08-02 20:28:04 +05:30
def clear_all_chats(self):
self.data["chats"] = []
self.win.load_threads()
2023-04-26 18:22:27 +05:30
def main(version):
"""The application's entry point."""
app = BavarderApplication()
return app.run(sys.argv)
2023-07-19 19:29:17 +05:30