diff --git a/src/connection_handler.py b/src/connection_handler.py index c0411e4..4ad9cba 100644 --- a/src/connection_handler.py +++ b/src/connection_handler.py @@ -22,14 +22,17 @@ def log_output(pipe): class instance(): - def __init__(self, local_port:int, remote_url:str, remote:bool, tweaks:dict, overrides:dict, bearer_token:str): + def __init__(self, local_port:int, remote_url:str, remote:bool, tweaks:dict, overrides:dict, bearer_token:str, idle_timer_delay:int): self.local_port=local_port self.remote_url=remote_url self.remote=remote self.tweaks=tweaks self.overrides=overrides self.bearer_token=bearer_token - self.instance = None + self.idle_timer_delay=idle_timer_delay + self.idle_timer_stop_event=threading.Event() + self.idle_timer=None + self.instance=None if not self.remote: self.start() @@ -42,11 +45,17 @@ class instance(): return headers if len(headers.keys()) > 0 else None def request(self, connection_type:str, connection_url:str, data:dict=None, callback:callable=None) -> requests.models.Response: + if self.idle_timer: + self.idle_timer_stop_event.set() + self.idle_timer=None + if not self.instance: + self.start() connection_url = '{}/{}'.format(self.remote_url if self.remote else 'http://127.0.0.1:{}'.format(self.local_port), connection_url) logger.info('Connection: {} : {}'.format(connection_type, connection_url)) + response = None match connection_type: case "GET": - return requests.get(connection_url, headers=self.get_headers(False)) + response = requests.get(connection_url, headers=self.get_headers(False)) case "POST": if callback: response = requests.post(connection_url, headers=self.get_headers(True), data=data, stream=True) @@ -54,29 +63,48 @@ class instance(): for line in response.iter_lines(): if line: callback(json.loads(line.decode("utf-8"))) - return response + response = response else: - return requests.post(connection_url, headers=self.get_headers(True), data=data, stream=False) + response = requests.post(connection_url, headers=self.get_headers(True), data=data, stream=False) case "DELETE": - return requests.delete(connection_url, headers=self.get_headers(False), json=data) + response = requests.delete(connection_url, headers=self.get_headers(False), json=data) + if not self.idle_timer: + self.start_timer() + return response + + def run_timer(self): + if not self.idle_timer_stop_event.wait(self.idle_timer_delay*60): + self.stop() + + def start_timer(self): + if self.idle_timer: + self.idle_timer_stop_event.set() + self.idle_timer=None + if self.idle_timer_delay > 0: + self.idle_timer_stop_event.clear() + self.idle_timer = threading.Thread(target=self.run_timer) + self.idle_timer.start() def start(self): if not os.path.isdir(os.path.join(cache_dir, 'tmp/ollama')): os.mkdir(os.path.join(cache_dir, 'tmp/ollama')) - + self.instance = None params = self.overrides.copy() params["OLLAMA_DEBUG"] = "1" params["OLLAMA_HOST"] = f"127.0.0.1:{self.local_port}" # You can't change this directly sorry :3 params["HOME"] = data_dir params["TMPDIR"] = os.path.join(cache_dir, 'tmp/ollama') - self.instance = subprocess.Popen(["ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True) - threading.Thread(target=log_output, args=(self.instance.stdout,)).start() - threading.Thread(target=log_output, args=(self.instance.stderr,)).start() + 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) logger.info("Started Alpaca's Ollama instance") v_str = subprocess.check_output("ollama -v", shell=True).decode('utf-8') logger.info('Ollama version: {}'.format(v_str.split('client version is ')[1].strip())) + self.instance = instance + if not self.idle_timer: + self.start_timer() def stop(self): if self.instance: diff --git a/src/window.py b/src/window.py index f204e85..afdd4da 100644 --- a/src/window.py +++ b/src/window.py @@ -109,6 +109,7 @@ class AlpacaWindow(Adw.ApplicationWindow): ollama_instance = None model_manager = None add_chat_button = Gtk.Template.Child() + instance_idle_timer = Gtk.Template.Child() background_switch = Gtk.Template.Child() remote_connection_switch = Gtk.Template.Child() @@ -275,6 +276,11 @@ class AlpacaWindow(Adw.ApplicationWindow): self.ollama_instance.tweaks[spin.get_name()] = value self.save_server_config() + @Gtk.Template.Callback() + def instance_idle_timer_changed(self, spin): + self.ollama_instance.idle_timer_delay = round(spin.get_value()) + self.save_server_config() + @Gtk.Template.Callback() def create_model_start(self, button): name = self.create_model_name.get_text().lower().replace(":", "") @@ -488,7 +494,8 @@ Generate a title following these rules: 'local_port': self.ollama_instance.local_port, 'run_on_background': self.background_switch.get_active(), 'model_tweaks': self.ollama_instance.tweaks, - 'ollama_overrides': self.ollama_instance.overrides + 'ollama_overrides': self.ollama_instance.overrides, + 'idle_timer': self.ollama_instance.idle_timer_delay } json.dump(data, f, indent=6) @@ -769,7 +776,7 @@ Generate a title following these rules: elif extension == 'pdf': self.attach_file(file.get_path(), 'pdf') - def prepare_alpaca(self, local_port:int, remote_url:str, remote:bool, tweaks:dict, overrides:dict, bearer_token:str, save:bool, show_launch_dialog:bool): + def prepare_alpaca(self, local_port:int, remote_url:str, remote:bool, tweaks:dict, overrides:dict, bearer_token:str, idle_timer_delay:int, save:bool, show_launch_dialog:bool): #Show launch dialog if show_launch_dialog: self.launch_dialog.present(self) @@ -777,7 +784,7 @@ Generate a title following these rules: #Instance self.launch_level_bar.set_value(0) self.launch_status.set_description(_('Loading instance')) - self.ollama_instance = connection_handler.instance(local_port, remote_url, remote, tweaks, overrides, bearer_token) + self.ollama_instance = connection_handler.instance(local_port, remote_url, remote, tweaks, overrides, bearer_token, idle_timer_delay) #User Preferences self.launch_level_bar.set_value(1) @@ -795,6 +802,7 @@ Generate a title following these rules: self.remote_connection_switch.set_sensitive(self.remote_connection_entry.get_text()) self.remote_bearer_token_entry.set_text(self.ollama_instance.bearer_token) self.remote_connection_switch.set_active(self.ollama_instance.remote) + self.instance_idle_timer.set_value(self.ollama_instance.idle_timer_delay) #Model Manager self.model_manager = model_widget.model_manager_container() @@ -877,10 +885,12 @@ Generate a title following these rules: with open(os.path.join(config_dir, "server.json"), "r", encoding="utf-8") as f: data = json.load(f) self.background_switch.set_active(data['run_on_background']) - threading.Thread(target=self.prepare_alpaca, args=(data['local_port'], data['remote_url'], data['run_remote'], data['model_tweaks'], data['ollama_overrides'], data['remote_bearer_token'], False, not data['run_remote'])).start() + if 'idle_timer' not in data: + data['idle_timer'] = 0 + threading.Thread(target=self.prepare_alpaca, args=(data['local_port'], data['remote_url'], data['run_remote'], data['model_tweaks'], data['ollama_overrides'], data['remote_bearer_token'], round(data['idle_timer']), False, not data['run_remote'])).start() except Exception as e: logger.error(e) - threading.Thread(target=self.prepare_alpaca, args=(11435, '', False, {'temperature': 0.7, 'seed': 0, 'keep_alive': 5}, {}, '', True, True)).start() + threading.Thread(target=self.prepare_alpaca, args=(11435, '', False, {'temperature': 0.7, 'seed': 0, 'keep_alive': 5}, {}, '', 0, True, True)).start() else: - threading.Thread(target=self.prepare_alpaca, args=(11435, '', False, {'temperature': 0.7, 'seed': 0, 'keep_alive': 5}, {}, '', True, False)).start() + threading.Thread(target=self.prepare_alpaca, args=(11435, '', False, {'temperature': 0.7, 'seed': 0, 'keep_alive': 5}, {}, '', 0, True, False)).start() self.welcome_dialog.present(self) diff --git a/src/window.ui b/src/window.ui index 60988a7..7ca7dad 100644 --- a/src/window.ui +++ b/src/window.ui @@ -390,6 +390,26 @@ + + + + + + timer + Idle Timer + Number of minutes the instance should remain idle before it is shut down (0 means it won't be shut down) + 0 + + + 0 + 60 + 5 + + + + + +