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 ( ' XdpGtk4 ' , ' 1.0 ' )
gi . require_version ( ' GtkSource ' , ' 5 ' )
from gi . repository import Gtk , Gio , Adw , Xdp , XdpGtk4 , GLib
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-05-10 20:44:25 +05:30
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-05-10 20:44:25 +05:30
2023-07-19 19:29:17 +05:30
if not os . path . exists ( self . data_path ) :
os . makedirs ( self . data_path )
2023-05-17 01:13:23 +05:30
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-20 22:49:10 +05:30
" providers " : { } ,
" models " : { }
2023-07-19 19:29:17 +05:30
}
2023-05-17 01:13:23 +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
2023-05-14 19:23:41 +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 ) ,
2023-05-14 19:23:41 +05:30
self . on_set_provider_action
)
2023-05-17 01:13:23 +05:30
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
2023-05-14 19:23:41 +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 ( )
2023-05-14 19:23:41 +05:30
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 :
2023-08-11 21:47:03 +05:30
for name , d in self . data [ " providers " ] . items ( ) :
print ( d )
2023-07-19 19:29:17 +05:30
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
2023-05-14 19:23:41 +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-05-14 19:23:41 +05:30
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-07-19 19:29:17 +05:30
preferences = PreferencesWindow ( self . win )
2023-04-27 04:02:45 +05:30
preferences . present ( )
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 )
2023-05-14 19:23:41 +05:30
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
2023-05-14 19:23:41 +05:30
self . add_action ( action )
2023-07-19 19:29:17 +05:30
2023-05-14 19:23:41 +05:30
if shortcuts :
2023-07-19 19:29:17 +05:30
self . set_accels_for_action ( f " app. { name } " , shortcuts )
2023-05-14 19:23:41 +05:30
2023-07-19 19:29:17 +05:30
def on_ask ( self , widget , _ ) :
try :
2023-08-03 18:38:12 +05:30
print ( " ASK-APP " )
2023-07-19 19:29:17 +05:30
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. """
2023-08-20 22:49:10 +05:30
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 ( )
2023-08-20 22:49:10 +05:30
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-08-20 22:49:10 +05:30
)
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
2023-08-20 22:49:10 +05:30
@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
2023-08-20 22:49:10 +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-08-02 23:49:45 +05:30
def load_bot_and_user_name ( self ) :
print ( self . bot_name )
print ( self . user_name )
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