mirror of
https://github.com/ParisNeo/lollms.git
synced 2025-03-15 16:45:53 +00:00
415 lines
20 KiB
Python
415 lines
20 KiB
Python
|
|
from lollms.helpers import ASCIIColors
|
|
from typing import TYPE_CHECKING
|
|
if TYPE_CHECKING:
|
|
from lollms.app import LollmsApplication
|
|
|
|
from lollms.binding import BindingBuilder
|
|
from lollms.config import InstallOption
|
|
from lollms.personality import PersonalityBuilder
|
|
from lollms.helpers import trace_exception
|
|
|
|
from tqdm import tqdm
|
|
import pkg_resources
|
|
from pathlib import Path
|
|
import yaml
|
|
import sys
|
|
class Menu:
|
|
"""Console menu tool that allows the user to select options."""
|
|
|
|
def __init__(self, name, options):
|
|
"""
|
|
Initialize a new menu instance.
|
|
|
|
Parameters:
|
|
name (str): The name of the menu.
|
|
options (list): A list of menu options, each represented as a dictionary
|
|
with 'name' as the option display name, 'fn' as the function
|
|
to be called when the user selects the option, an optional
|
|
'help' message to display a brief description of the option,
|
|
and an optional 'exit_after_exec' flag to exit the menu
|
|
automatically after executing the function.
|
|
"""
|
|
self.name = name
|
|
self.options = options
|
|
|
|
def show(self, menu_structure=None):
|
|
"""
|
|
Display the menu options to the user and handle the user's choice.
|
|
|
|
Parameters:
|
|
menu_structure (list, optional): A list of menu options to use instead of
|
|
the ones sent to the constructor. If None,
|
|
it uses the options sent to the constructor.
|
|
|
|
Note: If both `menu_structure` and options sent to the constructor are None,
|
|
the method will raise a ValueError.
|
|
"""
|
|
if menu_structure is None and not self.options:
|
|
raise ValueError("Menu options not provided.")
|
|
|
|
if menu_structure is not None:
|
|
current_options = menu_structure
|
|
else:
|
|
current_options = self.options
|
|
|
|
while True:
|
|
ASCIIColors.cyan(f"\n--- {self.name.upper()} MENU ---")
|
|
for i, option in enumerate(current_options, start=1):
|
|
ASCIIColors.yellow(f"{i}.",end="")
|
|
print(f" {option['name']}")
|
|
ASCIIColors.yellow("0.",end="")
|
|
print(" Go back to the previous menu" if self.name != "Main" else "0. Exit")
|
|
print("help")
|
|
|
|
choice = input("Select an option: ")
|
|
if choice.isdigit():
|
|
choice = int(choice)
|
|
if 0 <= choice <= len(current_options):
|
|
if choice == 0:
|
|
if self.name == "Main":
|
|
print("Exiting the menu.")
|
|
break
|
|
else:
|
|
return
|
|
else:
|
|
chosen_option = current_options[choice - 1]
|
|
sub_menu = chosen_option.get('sub_menu')
|
|
exit_after_exec = chosen_option.get('exit_after_exec', False)
|
|
if sub_menu:
|
|
self.show(sub_menu)
|
|
else:
|
|
chosen_option['fn']()
|
|
if exit_after_exec:
|
|
print(f"Exiting {self.name.upper()} menu.")
|
|
return
|
|
else:
|
|
print("Invalid option. Please select again.")
|
|
elif choice.lower() == "help":
|
|
self.display_help(current_options)
|
|
else:
|
|
print("Invalid input. Please enter a number or 'help' for assistance.")
|
|
|
|
def display_help(self, options):
|
|
"""
|
|
Display a brief description of each option available in the menu.
|
|
|
|
Parameters:
|
|
options (list): The list of menu options to display the help for.
|
|
"""
|
|
print(f"\n--- {self.name.upper()} MENU HELP ---")
|
|
for option in options:
|
|
help_msg = option.get('help', "No help available.")
|
|
print(f"{option['name']}: {help_msg}")
|
|
def yes_no_question(self, question):
|
|
"""
|
|
Ask the user a yes or no question and wait for a valid response.
|
|
|
|
Parameters:
|
|
question (str): The question to be displayed to the user.
|
|
|
|
Returns:
|
|
bool: True if the user answers 'yes', False if the user answers 'no'.
|
|
"""
|
|
while True:
|
|
response = input(f"{question} (yes/no): ").lower()
|
|
if response in ['y', 'yes']:
|
|
return True
|
|
elif response in ['n', 'no']:
|
|
return False
|
|
else:
|
|
print("Invalid response. Please answer with 'yes' or 'no' (or 'y'/'n').")
|
|
|
|
class MainMenu(Menu):
|
|
def __init__(self, lollms_app:'LollmsApplication', callback=None):
|
|
self.binding_infs = []
|
|
self.lollms_app = lollms_app
|
|
self.callback = callback
|
|
main_menu_options = [
|
|
{'name': 'Main settings', 'fn': self.main_settings, 'help': "Show main settings."},
|
|
{'name': 'Select Binding', 'fn': self.select_binding, 'help': "Choose a binding."},
|
|
{'name': 'Select Model', 'fn': self.select_model, 'help': "Choose a model."},
|
|
{'name': 'View mounted Personalities', 'fn': self.vew_mounted_personalities, 'help': "View all currently mounted personalities."},
|
|
{'name': 'Mount Personality', 'fn': self.mount_personality, 'help': "Mount a new personality."},
|
|
{'name': 'Unmount Personality', 'fn': self.unmount_personality, 'help': "Unmount a personality."},
|
|
{'name': 'Select Personality', 'fn': self.select_personality, 'help': "Choose a personality."},
|
|
{'name': 'Reinstall Binding', 'fn': self.reinstall_binding, 'help': "Reinstall the selected binding."},
|
|
{'name': 'Reinstall current Personality', 'fn': self.reinstall_personality, 'help': "Reinstall the current selected personality."},
|
|
{'name': 'Reset all installs', 'fn': self.lollms_app.reset_all_installs, 'help': "Reset all installed personalities."},
|
|
{'name': 'Reset paths', 'fn': self.lollms_app.lollms_paths.resetPaths, 'help': "Reset all paths to default."},
|
|
]
|
|
super().__init__("Lollms Main menu", main_menu_options)
|
|
|
|
def main_settings(self):
|
|
self.show([
|
|
{'name': 'Set user name', 'fn': self.set_user_name, 'help': "Sets the user name."},
|
|
{'name': 'Set use user name in discussion', 'fn': self.set_use_user_name_in_discussions, 'help': "Sets the user name."},
|
|
])
|
|
|
|
def set_user_name(self):
|
|
print(f"Current user name : {self.lollms_app.config.user_name}")
|
|
self.lollms_app.config.user_name = input("New user name:")
|
|
self.lollms_app.config.save_config()
|
|
|
|
def set_use_user_name_in_discussions(self):
|
|
ASCIIColors.info(f"Current status: {self.lollms_app.config.use_user_name_in_discussions}")
|
|
self.lollms_app.config.use_user_name_in_discussions = self.yes_no_question('Use user name in dicsussion')
|
|
self.lollms_app.config.save_config()
|
|
|
|
def show_logo(self):
|
|
print(f"{ASCIIColors.color_bright_yellow}")
|
|
print(" ___ ___ ___ ___ ___ ___ ")
|
|
print(" /\__\ /\ \ /\__\ /\__\ /\__\ /\ \ ")
|
|
print(" /:/ / /::\ \ /:/ / /:/ / /::| | /::\ \ ")
|
|
print(" /:/ / /:/\:\ \ /:/ / /:/ / /:|:| | /:/\ \ \ ")
|
|
print(" /:/ / /:/ \:\ \ /:/ / /:/ / /:/|:|__|__ _\:\~\ \ \ ")
|
|
print(" /:/__/ /:/__/ \:\__\ /:/__/ /:/__/ /:/ |::::\__\ /\ \:\ \ \__\ ")
|
|
print(" \:\ \ \:\ \ /:/ / \:\ \ \:\ \ \/__/~~/:/ / \:\ \:\ \/__/ ")
|
|
print(" \:\ \ \:\ /:/ / \:\ \ \:\ \ /:/ / \:\ \:\__\ ")
|
|
print(" \:\ \ \:\/:/ / \:\ \ \:\ \ /:/ / \:\/:/ / ")
|
|
print(" \:\__\ \::/ / \:\__\ \:\__\ /:/ / \::/ / ")
|
|
print(" \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ ")
|
|
|
|
|
|
|
|
|
|
print(f"{ASCIIColors.color_reset}")
|
|
print(f"{ASCIIColors.color_red}Version: {ASCIIColors.color_green}{pkg_resources.get_distribution('lollms').version}")
|
|
print(f"{ASCIIColors.color_red}By : {ASCIIColors.color_green}ParisNeo")
|
|
print(f"{ASCIIColors.color_reset}")
|
|
|
|
def show_commands_list(self):
|
|
print()
|
|
print("Commands:")
|
|
print(f" {ASCIIColors.color_red}├{ASCIIColors.color_reset} menu: shows main menu")
|
|
print(f" {ASCIIColors.color_red}├{ASCIIColors.color_reset} help: shows this info")
|
|
print(f" {ASCIIColors.color_red}├{ASCIIColors.color_reset} reset: resets the context")
|
|
print(f" {ASCIIColors.color_red}├{ASCIIColors.color_reset} <empty prompt>: forces the model to continue generating")
|
|
print(f" {ASCIIColors.color_red}├{ASCIIColors.color_reset} context_infos: current context size and space left before cropping")
|
|
print(f" {ASCIIColors.color_red}├{ASCIIColors.color_reset} start_log: starts logging the discussion to a text file")
|
|
print(f" {ASCIIColors.color_red}├{ASCIIColors.color_reset} stop_log: stops logging the discussion to a text file")
|
|
print(f" {ASCIIColors.color_red}├{ASCIIColors.color_reset} send_file: uploads a file to the AI")
|
|
print(f" {ASCIIColors.color_red}└{ASCIIColors.color_reset} exit: exists the console")
|
|
|
|
if self.lollms_app.personality:
|
|
if self.lollms_app.personality.help !="":
|
|
print(f"Personality help:")
|
|
print(f"{self.lollms_app.personality.help}")
|
|
|
|
|
|
|
|
def show_menu(self, options, title="Menu:", selection:int=None):
|
|
ASCIIColors.yellow(title)
|
|
for index, option in enumerate(options):
|
|
if selection is not None and index==selection:
|
|
print(f"{ASCIIColors.color_green}{index + 1} - {option}{ASCIIColors.color_reset}")
|
|
else:
|
|
print(f"{ASCIIColors.color_green}{index + 1} -{ASCIIColors.color_reset} {option}")
|
|
choice = input("Enter your choice: ")
|
|
return int(choice) if choice.isdigit() else -1
|
|
|
|
def select_binding(self):
|
|
bindings_list = []
|
|
print()
|
|
print(f"{ASCIIColors.color_green}Current binding: {ASCIIColors.color_reset}{self.lollms_app.config['binding_name']}")
|
|
for p in self.lollms_app.lollms_paths.bindings_zoo_path.iterdir():
|
|
if p.is_dir() and not p.stem.startswith("."):
|
|
with open(p/"binding_card.yaml", "r") as f:
|
|
card = yaml.safe_load(f)
|
|
with open(p/"models.yaml", "r") as f:
|
|
models = yaml.safe_load(f)
|
|
is_installed = (self.lollms_app.lollms_paths.personal_configuration_path/f"binding_{p.name}.yaml").exists()
|
|
entry=f"{ASCIIColors.color_green if is_installed else ''}{'*' if self.lollms_app.config['binding_name']==card['name'] else ''} {card['name']} (by {card['author']})"
|
|
bindings_list.append(entry)
|
|
entry={
|
|
"name":p.name,
|
|
"card":card,
|
|
"models":models,
|
|
"installed": is_installed
|
|
}
|
|
self.binding_infs.append(entry)
|
|
bindings_list += ["Back"]
|
|
choice = self.show_menu(bindings_list)
|
|
if 1 <= choice <= len(bindings_list)-1:
|
|
print(f"You selected binding: {ASCIIColors.color_green}{self.binding_infs[choice - 1]['name']}{ASCIIColors.color_reset}")
|
|
self.lollms_app.config['binding_name']=self.binding_infs[choice - 1]['name']
|
|
self.lollms_app.binding = self.lollms_app.load_binding()
|
|
self.lollms_app.config['model_name']=None
|
|
self.lollms_app.config.save_config()
|
|
elif choice <= len(bindings_list):
|
|
return
|
|
else:
|
|
print("Invalid choice!")
|
|
|
|
def select_model(self):
|
|
print()
|
|
print(f"{ASCIIColors.color_green}Current binding: {ASCIIColors.color_reset}{self.lollms_app.config['binding_name']}")
|
|
print(f"{ASCIIColors.color_green}Current model: {ASCIIColors.color_reset}{self.lollms_app.config['model_name']}")
|
|
|
|
models_dir:Path = (self.lollms_app.lollms_paths.personal_models_path/self.lollms_app.config['binding_name'])
|
|
models_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
models_list = self.lollms_app.binding.list_models(self.lollms_app.config) + ["Install model", "Change binding", "Back"]
|
|
choice = self.show_menu(models_list)
|
|
if 1 <= choice <= len(models_list)-3:
|
|
print(f"You selected model: {ASCIIColors.color_green}{models_list[choice - 1]}{ASCIIColors.color_reset}")
|
|
self.lollms_app.config['model_name']=models_list[choice - 1]
|
|
self.lollms_app.config.save_config()
|
|
self.lollms_app.load_model()
|
|
elif choice <= len(models_list)-2:
|
|
self.install_model()
|
|
elif choice <= len(models_list)-1:
|
|
self.select_binding()
|
|
self.select_model()
|
|
elif choice <= len(models_list):
|
|
return
|
|
else:
|
|
print("Invalid choice!")
|
|
|
|
def install_model(self):
|
|
|
|
models_list = ["Install model from internet","Install model from local file","Back"]
|
|
choice = self.show_menu(models_list)
|
|
if 1 <= choice <= len(models_list)-2:
|
|
url = input("Give a URL to the model to be downloaded :")
|
|
def progress_callback(blocks, block_size, total_size):
|
|
tqdm_bar.total=total_size
|
|
tqdm_bar.update(block_size)
|
|
|
|
# Usage example
|
|
with tqdm(total=100, unit="%", desc="Download Progress", ncols=80) as tqdm_bar:
|
|
self.lollms_app.config.download_model(url,self.lollms_app.binding, progress_callback)
|
|
self.select_model()
|
|
elif choice <= len(models_list)-1:
|
|
path = Path(input("Give a path to the model to be used on your PC:"))
|
|
if path.exists():
|
|
self.lollms_app.config.reference_model(path)
|
|
self.select_model()
|
|
elif choice <= len(models_list):
|
|
return
|
|
else:
|
|
print("Invalid choice!")
|
|
|
|
def mount_personality(self):
|
|
print()
|
|
ASCIIColors.red(f"Mounted personalities:")
|
|
for i,personality in enumerate(self.lollms_app.config['personalities']):
|
|
if i==self.lollms_app.config['active_personality_id']:
|
|
ASCIIColors.green(personality)
|
|
else:
|
|
ASCIIColors.yellow(personality)
|
|
personality_categories = [p.stem for p in (self.lollms_app.lollms_paths.personalities_zoo_path).iterdir() if p.is_dir() and not p.name.startswith(".")]+["Back"]
|
|
print("Select category")
|
|
choice = self.show_menu(personality_categories)
|
|
if 1 <= choice <= len(personality_categories)-1:
|
|
category = personality_categories[choice - 1]
|
|
print(f"You selected category: {ASCIIColors.color_green}{category}{ASCIIColors.color_reset}")
|
|
personality_names = [p.stem for p in (self.lollms_app.lollms_paths.personalities_zoo_path/category).iterdir() if p.is_dir() and not p.name.startswith(".")]+["Back"]
|
|
print("Select personality")
|
|
choice = self.show_menu(personality_names)
|
|
if 1 <= choice <= len(personality_names)-1:
|
|
name = personality_names[choice - 1]
|
|
print(f"You selected personality: {ASCIIColors.color_green}{name}{ASCIIColors.color_reset}")
|
|
self.lollms_app.config["personalities"].append(f"{category}/{name}")
|
|
self.lollms_app.mount_personality(len(self.lollms_app.config["personalities"])-1, callback = self.callback)
|
|
self.lollms_app.config.save_config()
|
|
print("Personality mounted successfully!")
|
|
elif 1 <= choice <= len(personality_names):
|
|
return
|
|
else:
|
|
print("Invalid choice!")
|
|
elif 1 <= choice <= len(personality_categories):
|
|
return
|
|
else:
|
|
print("Invalid choice!")
|
|
|
|
def vew_mounted_personalities(self):
|
|
ASCIIColors.info("Here is the list of mounted personalities")
|
|
entries = self.lollms_app.config['personalities']
|
|
for id, entry in enumerate(entries):
|
|
if id != self.lollms_app.config.active_personality_id:
|
|
ASCIIColors.yellow(f"{id+1} - {entry}")
|
|
else:
|
|
ASCIIColors.green(f"{id+1} - {entry}")
|
|
self.show_menu(["Back"])
|
|
|
|
|
|
def unmount_personality(self):
|
|
ASCIIColors.info("Select personality to unmount")
|
|
entries = self.lollms_app.config['personalities']+["Back"]
|
|
try:
|
|
choice = int(self.show_menu(entries, self.lollms_app.config['active_personality_id']))-1
|
|
if choice<len(entries)-1:
|
|
self.lollms_app.unmount_personality(choice)
|
|
except Exception as ex:
|
|
ASCIIColors.error(f"Couldn't uhnmount personality.\nGot this exception:{ex}")
|
|
|
|
def select_personality(self):
|
|
ASCIIColors.info("Select personality to activate")
|
|
entries = self.lollms_app.config['personalities']+["Back"]
|
|
try:
|
|
choice = int(self.show_menu(entries, self.lollms_app.config['active_personality_id']))-1
|
|
if choice<len(entries)-1:
|
|
self.lollms_app.select_personality(choice)
|
|
except Exception as ex:
|
|
ASCIIColors.error(f"Couldn't set personality.\nGot this exception:{ex}")
|
|
|
|
def reinstall_binding(self):
|
|
lollms_app = self.lollms_app
|
|
bindings_list = []
|
|
print()
|
|
print(f"{ASCIIColors.color_green}Current binding: {ASCIIColors.color_reset}{self.lollms_app.config['binding_name']}")
|
|
for p in self.lollms_app.lollms_paths.bindings_zoo_path.iterdir():
|
|
if p.is_dir() and not p.stem.startswith("."):
|
|
with open(p/"binding_card.yaml", "r") as f:
|
|
card = yaml.safe_load(f)
|
|
with open(p/"models.yaml", "r") as f:
|
|
models = yaml.safe_load(f)
|
|
is_installed = (self.lollms_app.lollms_paths.personal_configuration_path/f"binding_{p.name}.yaml").exists()
|
|
entry=f"{ASCIIColors.color_green if is_installed else ''}{'*' if self.lollms_app.config['binding_name']==card['name'] else ''} {card['name']} (by {card['author']})"
|
|
bindings_list.append(entry)
|
|
entry={
|
|
"name":p.name,
|
|
"card":card,
|
|
"models":models,
|
|
"installed": is_installed
|
|
}
|
|
self.binding_infs.append(entry)
|
|
bindings_list += ["Back"]
|
|
choice = self.show_menu(bindings_list)
|
|
if 1 <= choice <= len(bindings_list)-1:
|
|
print(f"You selected binding: {ASCIIColors.color_green}{self.binding_infs[choice - 1]['name']}{ASCIIColors.color_reset}")
|
|
self.lollms_app.config['binding_name']=self.binding_infs[choice - 1]['name']
|
|
|
|
try:
|
|
lollms_app.binding = BindingBuilder().build_binding(lollms_app.config, lollms_app.lollms_paths,InstallOption.FORCE_INSTALL)
|
|
except Exception as ex:
|
|
print(ex)
|
|
print(f"Couldn't find binding. Please verify your configuration file at {lollms_app.config.file_path} or use the next menu to select a valid binding")
|
|
self.select_binding()
|
|
|
|
self.lollms_app.config['model_name']=None
|
|
self.lollms_app.config.save_config()
|
|
elif choice <= len(bindings_list):
|
|
return
|
|
else:
|
|
print("Invalid choice!")
|
|
|
|
|
|
|
|
def reinstall_personality(self, callback=None):
|
|
lollms_app = self.lollms_app
|
|
try:
|
|
lollms_app.personality = PersonalityBuilder(lollms_app.lollms_paths, lollms_app.config, lollms_app.model, installation_option=InstallOption.FORCE_INSTALL, callback=callback).build_personality()
|
|
except Exception as ex:
|
|
ASCIIColors.error(f"Couldn't load personality. Please verify your configuration file at {lollms_app.configuration_path} or use the next menu to select a valid personality")
|
|
ASCIIColors.error(f"Binding returned this exception : {ex}")
|
|
trace_exception(ex)
|
|
ASCIIColors.error(f"{lollms_app.config.get_personality_path_infos()}")
|
|
print("Please select a valid model or install a new one from a url")
|
|
self.select_model()
|
|
|
|
def main_menu(self):
|
|
self.show()
|
|
|