[IMP] bot assistant TODO

This commit is contained in:
Mathieu Benoit 2025-04-26 17:12:38 -04:00
parent 820567b48a
commit 7dc8da3f02
8 changed files with 415 additions and 1 deletions

3
.gitignore vendored
View file

@ -41,4 +41,5 @@ pyproject.toml
.python-version
requirements.txt
requirement/ignore_requirements.txt
screenshots
screenshots
script/todo/todo_override.json

View file

@ -725,6 +725,14 @@ open_test_coverage:
# TODO load specific test file : ./run.sh -d test_file --log-level=test --test-enable --stop-after-init --test-file ./.venv/test.py
########
# TODO #
########
.PHONY: todo
todo:
./script/todo/todo.py
#########
# tag #
#########

View file

@ -28,4 +28,5 @@ create demo internal,./script/code_generator/new_project.py,-m demo_internal -d
run_robotlibre,./odoo/odoo-bin,--limit-time-real 999999 -c config.conf -d robotlibre,robotlibre,
run_robotlibre_update,./odoo/odoo-bin,--limit-time-real 999999 --no-http --stop-after-init --dev cg -d robotlibre -i erplibre_devops -u erplibre_devops,robotlibre,
run_robotlibre_generate,./script/code_generator/new_project.py,-f -d ./addons/ERPLibre_erplibre_addons -m erplibre_devops,robotlibre,
run_todo,./script/todo/todo.py,,TODO,

1 name script_path parameters folder default
28 run_robotlibre ./odoo/odoo-bin --limit-time-real 999999 -c config.conf -d robotlibre robotlibre
29 run_robotlibre_update ./odoo/odoo-bin --limit-time-real 999999 --no-http --stop-after-init --dev cg -d robotlibre -i erplibre_devops -u erplibre_devops robotlibre
30 run_robotlibre_generate ./script/code_generator/new_project.py -f -d ./addons/ERPLibre_erplibre_addons -m erplibre_devops robotlibre
31 run_todo ./script/todo/todo.py TODO
32

2
script/todo/README.md Normal file
View file

@ -0,0 +1,2 @@
TODO is an assistant robot to use ERPLibre
Execute it with `./script/todo/todo.py` or `make todo`.

View file

@ -0,0 +1,9 @@
__
_(\ |@@|
(__/\__ \--/ __
\___|----| | __
\ }{ /\ )_ / _\
/\__/\ \__O (__
(--/\--) \__/
_)( )(_
`---''---`

23
script/todo/todo.json Normal file
View file

@ -0,0 +1,23 @@
{
"kdbx": {
"path": "",
"password": ""
},
"kdbx_config": {
"openai": {
"kdbx_key": "OpenAI api"
}
},
"instance": [
{
"prompt_description": "Test - Instance de base minimale",
"makefile_cmd": "db_restore_erplibre_base_db_test",
"database": "test"
}
],
"function": [
{
"prompt_description": "Ouvrir ERPLibre avec TODO \uD83E\uDD16"
}
]
}

335
script/todo/todo.py Executable file
View file

@ -0,0 +1,335 @@
#!/usr/bin/env python
import getpass
import json
import logging
import os
import subprocess
import tkinter as tk
from tkinter import filedialog
import click
import openai
from pykeepass import PyKeePass
# TODO implement urwid to improve text user interface
# import urwid
_logger = logging.getLogger(__name__)
CONFIG_FILE = "./script/todo/todo.json"
CONFIG_OVERRIDE_FILE = "./script/todo/todo_override.json"
LOGO_ASCII_FILE = "./script/todo/logo_ascii.txt"
class TODO:
def __init__(self):
self.kdbx = None
def run(self):
with open(LOGO_ASCII_FILE) as my_file:
print(my_file.read())
print("Ouverture de TODO en cours ...")
print("🤖 => Entre tes directives par son chiffre et fait Entrée!")
help_info = """Commande :
[1] Question
[2] Execute
[3] Fork - Ouvre TODO 🤖 dans une nouvelle tabulation
[0] Quitter
"""
# [3] install
while True:
status = click.prompt(help_info)
print()
if status == "0":
break
elif status == "1":
self.execute_prompt_ia()
elif status == "2":
self.prompt_execute()
elif status == "3":
cmd = f"gnome-terminal --tab -- bash -c 'source ./.venv/bin/activate;make todo'"
self.executer_commande_live(cmd)
# elif status == "3" or status == "install":
# print("install")
else:
print("Commande non trouvée 🤖!")
print(status)
# manipuler()
def get_kdbx(self):
if self.kdbx:
return self.kdbx
# Open file
chemin_fichier_kdbx = self.get_config(["kdbx", "path"])
if not chemin_fichier_kdbx:
root = tk.Tk()
root.withdraw() # Hide the main window
chemin_fichier_kdbx = filedialog.askopenfilename(
title="Select a File",
filetypes=(("KeepassX files", "*.kdbx"),),
)
if not chemin_fichier_kdbx:
_logger.error(f"KDBX is not configured, please fill {CONFIG_FILE}")
return
mot_de_passe_kdbx = self.get_config(["kdbx", "password"])
if not mot_de_passe_kdbx:
mot_de_passe_kdbx = getpass.getpass(
prompt="Entrez votre mot de passe : "
)
kp = PyKeePass(chemin_fichier_kdbx, password=mot_de_passe_kdbx)
if kp:
self.kdbx = kp
return kp
def get_config(self, lst_params):
# Open file
config_file = CONFIG_FILE
if os.path.exists(CONFIG_OVERRIDE_FILE):
config_file = CONFIG_OVERRIDE_FILE
with open(config_file) as cfg:
dct_data = json.load(cfg)
for param in lst_params:
try:
dct_data = dct_data[param]
except KeyError:
_logger.error(
f"KeyError on file {config_file} with keys {lst_params}"
)
return
return dct_data
def execute_prompt_ia(self):
while True:
help_info = """Commande :
[0] Retour
Écrit moi ta question """
status = click.prompt(help_info)
print()
if status == "0":
return
kp = self.get_kdbx()
if not kp:
return
nom_configuration = self.get_config(
["kdbx_config", "openai", "kdbx_key"]
)
entry = kp.find_entries_by_title(nom_configuration, first=True)
client = openai.OpenAI(api_key=entry.password)
prompt_update = status
completion = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt_update}],
)
print(completion.choices[0].message.content)
print()
def prompt_execute(self):
help_info = """Commande :
[1] Gérer une instance
[2] Fonction - Démonstration des fonctions développées
[0] Retour
"""
while True:
status = click.prompt(help_info)
print()
if status == "0":
return
elif status == "1":
status = self.prompt_execute_instance()
if status is not False:
return
elif status == "2":
status = self.prompt_execute_fonction()
if status is not False:
return
else:
print("Commande non trouvée 🤖!")
def prompt_execute_instance(self):
lst_instance = self.get_config(["instance"])
help_info = "Commande :\n"
help_end = "[0] Retour\n"
for i, dct_instance in enumerate(lst_instance):
help_info += (
f"[{i + 1}] " + dct_instance["prompt_description"] + "\n"
)
help_info += help_end
while True:
status = click.prompt(help_info)
print()
if status == "0":
return False
else:
cmd_no_found = True
try:
int_cmd = int(status)
if 0 < int_cmd <= len(lst_instance):
cmd_no_found = False
status = click.confirm(
"Voulez-vous une nouvelle instance?"
)
if status:
dct_instance = lst_instance[int_cmd - 1]
makefile_cmd = dct_instance.get("makefile_cmd")
db_name = dct_instance.get("database")
self.executer_commande_live(f"make {makefile_cmd}")
kdbx_key = dct_instance.get("kdbx_key")
self.prompt_execute_selenium_and_run_db(
db_name, kdbx_key=kdbx_key
)
except ValueError:
pass
if cmd_no_found:
print("Commande non trouvée 🤖!")
def prompt_execute_fonction(self):
lst_instance = self.get_config(["function"])
help_info = "Commande :\n"
help_end = "[0] Retour\n"
for i, dct_instance in enumerate(lst_instance):
help_info += (
f"[{i + 1}] " + dct_instance["prompt_description"] + "\n"
)
help_info += help_end
while True:
status = click.prompt(help_info)
print()
if status == "0":
return False
else:
cmd_no_found = True
try:
int_cmd = int(status)
if 0 < int_cmd <= len(lst_instance):
cmd_no_found = False
dct_instance = lst_instance[int_cmd - 1]
command = dct_instance.get("command")
kdbx_key = dct_instance.get("kdbx_key")
self.prompt_execute_selenium(
command=command, kdbx_key=kdbx_key
)
except ValueError:
pass
if cmd_no_found:
print("Commande non trouvée 🤖!")
def kdbx_get_extra_command_user(self, kdbx_key):
lst_value = []
if kdbx_key:
kp = self.get_kdbx()
if not kp:
return ""
if type(kdbx_key) is not list:
lst_kdbx_key = [kdbx_key]
else:
lst_kdbx_key = kdbx_key
for key in lst_kdbx_key:
entry = kp.find_entries_by_title(key, first=True)
try:
odoo_user = entry.username
except AttributeError:
_logger.error(f"Cannot find username from keys {key}")
try:
odoo_password = entry.password
except AttributeError:
_logger.error(f"Cannot find password from keys {key}")
lst_value.append(
f" --default_email_auth {odoo_user} --default_password_auth '{odoo_password}'"
)
if len(lst_value) == 0:
return ""
elif len(lst_value) == 1:
return lst_value[0]
return lst_value
def prompt_execute_selenium_and_run_db(self, bd, kdbx_key=None):
if kdbx_key:
extra_cmd_web_login = self.kdbx_get_extra_command_user(kdbx_key)
else:
extra_cmd_web_login = ""
cmd = f'parallel ::: "./run.sh -d {bd}" "sleep 3;./script/selenium/web_login.py{extra_cmd_web_login}"'
self.executer_commande_live(cmd)
def prompt_execute_selenium(self, command=None, kdbx_key=None):
if kdbx_key:
extra_cmd_web_login = self.kdbx_get_extra_command_user(kdbx_key)
else:
extra_cmd_web_login = ""
lst_cmd = []
if not command:
cmd = "./script/selenium/web_login.py"
else:
cmd = command
if type(extra_cmd_web_login) is list:
for item in extra_cmd_web_login:
lst_cmd.append(cmd + item)
else:
lst_cmd.append(cmd + extra_cmd_web_login)
if len(lst_cmd) == 1:
self.executer_commande_live(lst_cmd[0])
elif len(lst_cmd) > 1:
new_cmd = "parallel ::: "
for i, cmd in enumerate(lst_cmd):
new_cmd += f' "sleep {1 * i};{cmd}"'
self.executer_commande_live(new_cmd)
def executer_commande_live(self, commande):
"""
Exécute une commande et affiche la sortie en direct.
Args:
commande (str): La commande à exécuter (sous forme de chaîne de caractères).
"""
try:
process = subprocess.Popen(
commande,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1, # Désactive la mise en tampon pour la sortie en direct
universal_newlines=True, # Pour traiter les sauts de lignes correctement
)
while True:
ligne = process.stdout.readline()
if not ligne:
break
print(ligne, end="")
process.wait() # Attendre la fin du process
if process.returncode != 0:
print(
f"La commande a retourné un code d'erreur : {process.returncode}"
)
except FileNotFoundError:
if "password" in commande:
print(
f"Erreur : La commande '{commande.split(' ')[0]}'[...] n'a pas été trouvée."
)
else:
print(
f"Erreur : La commande '{commande}' n'a pas été trouvée."
)
except Exception as e:
print(f"Une erreur s'est produite : {e}")
if __name__ == "__main__":
todo = TODO()
todo.run()

View file

@ -0,0 +1,35 @@
{
"kdbx": {
"path": "",
"password": ""
},
"kdbx_config": {
"openai": {
"kdbx_key": "OpenAI api"
}
},
"instance": [
{
"prompt_description": "Test - Instance de base minimale",
"makefile_cmd": "db_restore_erplibre_base_db_test",
"database": "test",
"kdbx_key": "default_user_odoo"
}
],
"function": [
{
"prompt_description": "Ouvrir ERPLibre avec TODO \uD83E\uDD16"
},
{
"prompt_description": "Ouvrir ERPLibre avec tous les utilisateurs",
"kdbx_key": [
"user 01prod",
"user 02 prod"
]
},
{
"prompt_description": "Rapport",
"command": "./script/selenium/selenium_test.py --test --scenario=rapport"
}
]
}