io4it 1.0.1.1__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {io4it-1.0.1.1 → io4it-1.0.3}/PKG-INFO +1 -1
  2. {io4it-1.0.1.1 → io4it-1.0.3}/io4it.egg-info/PKG-INFO +1 -1
  3. {io4it-1.0.1.1 → io4it-1.0.3}/io4it.egg-info/SOURCES.txt +6 -0
  4. io4it-1.0.3/orangecontrib/IO4IT/utils/mail.py +241 -0
  5. io4it-1.0.3/orangecontrib/IO4IT/utils/offuscation_basique.py +126 -0
  6. io4it-1.0.3/orangecontrib/IO4IT/widgets/OWDeep_Search.py +208 -0
  7. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWS3Uploader.py +7 -0
  8. io4it-1.0.3/orangecontrib/IO4IT/widgets/designer/owdeepsearch.ui +74 -0
  9. io4it-1.0.3/orangecontrib/IO4IT/widgets/icons/deepsearch.svg +10 -0
  10. io4it-1.0.3/orangecontrib/IO4IT/widgets/icons_dev/__init__.py +0 -0
  11. {io4it-1.0.1.1 → io4it-1.0.3}/setup.py +1 -1
  12. {io4it-1.0.1.1 → io4it-1.0.3}/io4it.egg-info/dependency_links.txt +0 -0
  13. {io4it-1.0.1.1 → io4it-1.0.3}/io4it.egg-info/entry_points.txt +0 -0
  14. {io4it-1.0.1.1 → io4it-1.0.3}/io4it.egg-info/namespace_packages.txt +0 -0
  15. {io4it-1.0.1.1 → io4it-1.0.3}/io4it.egg-info/requires.txt +0 -0
  16. {io4it-1.0.1.1 → io4it-1.0.3}/io4it.egg-info/top_level.txt +0 -0
  17. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/__init__.py +0 -0
  18. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/ocr_function/__init__.py +0 -0
  19. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/ocr_function/word_converter.py +0 -0
  20. {io4it-1.0.1.1/orangecontrib/IO4IT/widgets/designer → io4it-1.0.3/orangecontrib/IO4IT/utils}/__init__.py +0 -0
  21. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWExportMarkdown.py +0 -0
  22. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWMarkdownizer.py +0 -0
  23. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWS3downloader.py +0 -0
  24. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWS3list.py +0 -0
  25. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWSpeechToText.py +0 -0
  26. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWmailLoader.py +0 -0
  27. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWmailSender.py +0 -0
  28. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/OWwordpdf2docx.py +0 -0
  29. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/__init__.py +0 -0
  30. {io4it-1.0.1.1/orangecontrib/IO4IT/widgets/icons → io4it-1.0.3/orangecontrib/IO4IT/widgets/designer}/__init__.py +0 -0
  31. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/chart.html +0 -0
  32. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/nogui.ui +0 -0
  33. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/ow_file_ext_selector.ui +0 -0
  34. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/owexportmarkdown.ui +0 -0
  35. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/owmailloader.ui +0 -0
  36. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/owmailsender.ui +0 -0
  37. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui +0 -0
  38. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/owvisualizationer.ui +0 -0
  39. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui +0 -0
  40. {io4it-1.0.1.1/orangecontrib/IO4IT/widgets/icons_dev → io4it-1.0.3/orangecontrib/IO4IT/widgets/icons}/__init__.py +0 -0
  41. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/download.png +0 -0
  42. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/export_md.png +0 -0
  43. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/file_extensor.png +0 -0
  44. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/list_aws.png +0 -0
  45. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/mail_loader.png +0 -0
  46. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/mail_writer.png +0 -0
  47. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/md.png +0 -0
  48. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/speech_to_text.png +0 -0
  49. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/upload.png +0 -0
  50. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/visualizationer.png +0 -0
  51. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/IO4IT/widgets/icons/wordpdf2docx.png +0 -0
  52. {io4it-1.0.1.1 → io4it-1.0.3}/orangecontrib/__init__.py +0 -0
  53. {io4it-1.0.1.1 → io4it-1.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: io4it
3
- Version: 1.0.1.1
3
+ Version: 1.0.3
4
4
  Home-page:
5
5
  Author:
6
6
  Author-email:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: io4it
3
- Version: 1.0.1.1
3
+ Version: 1.0.3
4
4
  Home-page:
5
5
  Author:
6
6
  Author-email:
@@ -11,6 +11,10 @@ orangecontrib/__init__.py
11
11
  orangecontrib/IO4IT/__init__.py
12
12
  orangecontrib/IO4IT/ocr_function/__init__.py
13
13
  orangecontrib/IO4IT/ocr_function/word_converter.py
14
+ orangecontrib/IO4IT/utils/__init__.py
15
+ orangecontrib/IO4IT/utils/mail.py
16
+ orangecontrib/IO4IT/utils/offuscation_basique.py
17
+ orangecontrib/IO4IT/widgets/OWDeep_Search.py
14
18
  orangecontrib/IO4IT/widgets/OWExportMarkdown.py
15
19
  orangecontrib/IO4IT/widgets/OWMarkdownizer.py
16
20
  orangecontrib/IO4IT/widgets/OWS3Uploader.py
@@ -25,6 +29,7 @@ orangecontrib/IO4IT/widgets/designer/__init__.py
25
29
  orangecontrib/IO4IT/widgets/designer/chart.html
26
30
  orangecontrib/IO4IT/widgets/designer/nogui.ui
27
31
  orangecontrib/IO4IT/widgets/designer/ow_file_ext_selector.ui
32
+ orangecontrib/IO4IT/widgets/designer/owdeepsearch.ui
28
33
  orangecontrib/IO4IT/widgets/designer/owexportmarkdown.ui
29
34
  orangecontrib/IO4IT/widgets/designer/owmailloader.ui
30
35
  orangecontrib/IO4IT/widgets/designer/owmailsender.ui
@@ -32,6 +37,7 @@ orangecontrib/IO4IT/widgets/designer/owspeechtotext.ui
32
37
  orangecontrib/IO4IT/widgets/designer/owvisualizationer.ui
33
38
  orangecontrib/IO4IT/widgets/designer/wordpdf2docx.ui
34
39
  orangecontrib/IO4IT/widgets/icons/__init__.py
40
+ orangecontrib/IO4IT/widgets/icons/deepsearch.svg
35
41
  orangecontrib/IO4IT/widgets/icons/download.png
36
42
  orangecontrib/IO4IT/widgets/icons/export_md.png
37
43
  orangecontrib/IO4IT/widgets/icons/file_extensor.png
@@ -0,0 +1,241 @@
1
+ import imaplib
2
+ import smtplib
3
+ import email
4
+ from email.header import decode_header
5
+ import time
6
+ import os
7
+ from email import policy
8
+ from email.message import EmailMessage
9
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
10
+ from Orange.widgets.orangecontrib.AAIT.utils import MetManagement
11
+ from Orange.widgets.orangecontrib.IO4IT.utils import offuscation_basique
12
+ else:
13
+ from orangecontrib.AAIT.utils import MetManagement
14
+ from orangecontrib.IO4IT.utils import offuscation_basique
15
+
16
+
17
+ def clean_addresses(field,myemail):
18
+ """Retourne une liste d'adresses nettoyées sans ton adresse"""
19
+ if not field:
20
+ return []
21
+ addresses = email.utils.getaddresses([field])
22
+ return [addr for name, addr in addresses if addr.lower() != myemail.lower()]
23
+
24
+ def mail_in_folder(agent_name, type="in"):
25
+ if agent_name is None or agent_name == "":
26
+ print("agent_name doit etre renseigné")
27
+ return None
28
+ chemin_dossier= MetManagement.get_path_mailFolder()
29
+ if type == "in":
30
+ if not os.path.exists(chemin_dossier):
31
+ os.makedirs(chemin_dossier)
32
+ real_time = MetManagement.get_second_from_1970()
33
+ folder_in = chemin_dossier + "/" + str(agent_name) + "/in/" + str(real_time)
34
+ folder_out = chemin_dossier + "/" + str(agent_name) + "/out/" + str(real_time)
35
+ if not os.path.exists(folder_in) and not os.path.exists(folder_out):
36
+ os.makedirs(folder_in)
37
+ os.makedirs(folder_out)
38
+ else:
39
+ time.sleep(1.5)
40
+ mail_in_folder(agent_name, "in")
41
+ return folder_in
42
+ if type == "out":
43
+ return chemin_dossier + str(agent_name) + "/in/", chemin_dossier + "/" + str(agent_name) + "/out/"
44
+
45
+
46
+ def check_new_emails(offusc_conf_agent):
47
+ try:
48
+ agent,my_domain,password,interl_seconds=offuscation_basique.lire_config(offusc_conf_agent)
49
+ myemail=agent + my_domain
50
+ imap = imaplib.IMAP4_SSL("imap.gmail.com")
51
+ imap.login(myemail, password)
52
+ imap.select("inbox")
53
+
54
+ status, messages = imap.search(None, 'UNSEEN')
55
+ mail_ids = messages[0].split()
56
+
57
+ if not mail_ids:
58
+ print("Aucun nouveau mail.")
59
+ else:
60
+ for mail_id in mail_ids:
61
+ time.sleep(1.5)
62
+ output_lines = []
63
+ _, msg_data = imap.fetch(mail_id, "(RFC822)")
64
+ for response_part in msg_data:
65
+ if isinstance(response_part, tuple):
66
+ from email.parser import BytesParser
67
+
68
+ # Utilise :
69
+ msg = BytesParser(policy=policy.default).parsebytes(response_part[1])
70
+
71
+ # Sujet
72
+ subject, encoding = decode_header(msg["Subject"])[0]
73
+ if isinstance(subject, bytes):
74
+ subject = subject.decode(encoding if encoding else "utf-8")
75
+
76
+ # Expéditeur
77
+ from_ = msg.get("From")
78
+
79
+ # Destinataires
80
+ to_emails = clean_addresses(msg.get("To", ""),myemail)
81
+ cc_emails = clean_addresses(msg.get("Cc", ""),myemail)
82
+
83
+ # Corps
84
+ body = ""
85
+ if msg.is_multipart():
86
+ for part in msg.walk():
87
+ content_type = part.get_content_type()
88
+ content_disposition = str(part.get("Content-Disposition"))
89
+
90
+ if content_type == "text/plain" and "attachment" not in content_disposition:
91
+ payload = part.get_payload(decode=True)
92
+ charset = part.get_content_charset()
93
+ body = payload.decode(charset if charset else "utf-8", errors="replace")
94
+ break
95
+ else:
96
+ body = msg.get_payload(decode=True).decode(errors="replace")
97
+
98
+ # Format de sortie
99
+ output_lines.append(f"#$who : {myemail}")
100
+ output_lines.append(f"#$eme : {from_}")
101
+ output_lines.append(f"#$des : {', '.join(to_emails)}")
102
+ output_lines.append(f"#$cop : {', '.join(cc_emails)}")
103
+ output_lines.append("#$pri : Normale")
104
+ output_lines.append(f"#$tit : {subject}")
105
+ output_lines.append(f"#$txt : {body.strip()}")
106
+ output_lines.append("")
107
+ print("----------------------------------------")
108
+ print(f"mail recu de {from_}")
109
+ print("----------------------------------------")
110
+
111
+ if output_lines != []:
112
+ folder = mail_in_folder(agent, "in")
113
+ if folder is None:
114
+ print("erreur dans le folder de mail")
115
+ return
116
+
117
+ with open(folder + "/" + "mail.txt", "w", encoding="utf-8") as f:
118
+ f.write("\n".join(output_lines))
119
+ f.close()
120
+
121
+ with open(folder + "/" + "mail.ok", "w") as f:
122
+ f.close()
123
+
124
+ for part in msg.iter_attachments():
125
+ filename = part.get_filename()
126
+ if filename:
127
+ folder_pj = folder + "/" + "pj"
128
+ if not os.path.exists(folder_pj):
129
+ os.makedirs(folder_pj)
130
+ filepath = os.path.join(folder_pj, filename)
131
+ with open(filepath, "wb") as f:
132
+ f.write(part.get_payload(decode=True))
133
+ f.close()
134
+
135
+ imap.logout()
136
+ except Exception as e:
137
+ print(f"Erreur lors de la vérification des mails : {e}")
138
+
139
+
140
+ def lire_message(chemin_fichier):
141
+ donnees = {}
142
+ cle_courante = None
143
+ texte_multi_ligne = []
144
+
145
+ with open(chemin_fichier, 'r', encoding='utf-8') as fichier:
146
+ for ligne in fichier:
147
+ if ligne.startswith('#$'):
148
+ if cle_courante == 'txt' and texte_multi_ligne:
149
+ donnees['txt'] = '\n'.join(texte_multi_ligne).strip()
150
+ texte_multi_ligne = []
151
+
152
+ cle_val = ligne[2:].split(':', 1)
153
+ cle_courante = cle_val[0].strip()
154
+
155
+ if cle_courante == 'txt':
156
+ texte_multi_ligne.append(cle_val[1].strip())
157
+ else:
158
+ donnees[cle_courante] = cle_val[1].strip()
159
+ else:
160
+ if cle_courante == 'txt':
161
+ texte_multi_ligne.append(ligne.rstrip())
162
+
163
+ # En fin de fichier, enregistrer le texte si encore en cours
164
+ if cle_courante == 'txt' and texte_multi_ligne:
165
+ donnees['txt'] = '\n'.join(texte_multi_ligne).strip()
166
+
167
+ return donnees
168
+
169
+
170
+
171
+
172
+ def send_mail(expediteur, offusc_conf_agent, destinataire, sujet, contenu,piece_jointe_path=None, serveur="smtp.gmail.com", port=587):
173
+ msg = EmailMessage()
174
+ msg['From'] = expediteur
175
+ msg['To'] = destinataire
176
+ msg['Subject'] = sujet
177
+ msg.set_content(contenu)
178
+ # Ajout d'une pièce jointe si fournie
179
+ if piece_jointe_path:
180
+ with open(piece_jointe_path, 'rb') as f:
181
+ data = f.read()
182
+ nom_fichier = piece_jointe_path.split('/')[-1]
183
+ msg.add_attachment(data, maintype='application', subtype='octet-stream', filename=nom_fichier)
184
+ try:
185
+ _, _, mot_de_passe, _ = offuscation_basique.lire_config(offusc_conf_agent)
186
+ with smtplib.SMTP(serveur, port) as smtp:
187
+ smtp.starttls()
188
+ smtp.login(expediteur, mot_de_passe)
189
+ smtp.send_message(msg)
190
+ return 0
191
+ except Exception as e:
192
+ print("❌ Une erreur s'est produite :", e)
193
+
194
+
195
+ def check_send_new_emails(offusc_conf_agent):
196
+ agent, domain, _, _ = offuscation_basique.lire_config(offusc_conf_agent)
197
+ chemin_dossier_in, chemin_dossier_out = mail_in_folder(agent, "out")
198
+ if os.path.exists(chemin_dossier_out) and os.path.isdir(chemin_dossier_out):
199
+ contenus = os.listdir(chemin_dossier_out)
200
+ if contenus:
201
+ for contenu in contenus:
202
+ if os.path.exists(chemin_dossier_out + "/" + contenu + "/mail.ok"):
203
+ chemin = chemin_dossier_out + "/" + contenu + "/mail.txt"
204
+ infos = lire_message(chemin)
205
+ # Affichage des informations extraites
206
+ cles_requises = ["eme", "des", "cop", "pri", "tit", "txt"]
207
+ if all(cle in infos for cle in cles_requises):
208
+ send_mail(
209
+ agent+domain,
210
+ offusc_conf_agent,
211
+ infos["eme"],
212
+ infos["tit"],
213
+ infos["txt"],
214
+ piece_jointe_path=None #à rajouter quand PJ ok chemin_dossier_out + "/" + contenu + "/pj"
215
+ )
216
+ MetManagement.reset_folder(chemin_dossier_in + contenu , recreate=False)
217
+ MetManagement.reset_folder(chemin_dossier_out + contenu, recreate=False)
218
+ else:
219
+ print("il manque des clefs dans le contenu du mail")
220
+ else:
221
+ print("Le dossier est vide.")
222
+ else:
223
+ print("Le dossier n'existe pas ou le chemin n'est pas un dossier.")
224
+
225
+
226
+ if __name__ == "__main__":
227
+ offusc_conf_agent="agent.ia_at_institut-ia.com.json"
228
+ while True:
229
+ check_new_emails(offusc_conf_agent)
230
+ time.sleep(1)
231
+ check_send_new_emails(offusc_conf_agent)
232
+ time.sleep(1)
233
+ # print(f"Surveillance des mails toutes les {INTERVAL_SECONDS} secondes. Ctrl+C pour arrêter.")
234
+ # send_mail(
235
+ # expediteur=EMAIL,
236
+ # mot_de_passe=PASSWORD,
237
+ # destinataire="jc@institut-ia.com",
238
+ # sujet="Test depuis Python",
239
+ # contenu="Bonjour,\nVoici un test d'envoi de mail avec Python 2.",
240
+ # piece_jointe_path=r""#C:\Users\max83\Desktop\Orange_4All_AAIT\Orange_4All_AAIT\Orange\Lib\site-packages\Orange\widgets\orangecontrib\HLIT_dev\widgets\icons\input_interface.png" # ou "chemin/vers/fichier.pdf"
241
+ # )
@@ -0,0 +1,126 @@
1
+ import os
2
+ import json
3
+ import hashlib
4
+ import getpass
5
+
6
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
7
+ from Orange.widgets.orangecontrib.AAIT.utils import MetManagement
8
+ else:
9
+ from orangecontrib.AAIT.utils import MetManagement
10
+
11
+ # Fonction pour générer une clé simple à partir du nom d'utilisateur
12
+ def get_user_key():
13
+ try:
14
+ try:
15
+ username = os.getlogin()
16
+ except OSError:
17
+ username = getpass.getuser()
18
+
19
+ if not username:
20
+ raise ValueError("Nom d'utilisateur introuvable")
21
+
22
+ # On dérive une clé simple (1 octet) depuis le hash du nom d'utilisateur
23
+ digest = hashlib.sha256(username.encode("utf-8")).digest()
24
+ key = digest[0] # 1 octet pour XOR
25
+ return key
26
+
27
+ except Exception as e:
28
+ raise RuntimeError(f"Erreur de génération de clé : {e}")
29
+
30
+ # Fonction simple de chiffrement/déchiffrement par XOR (non sécurisé mais obscurcissant)
31
+ def xor_crypt(data: str, key: int) -> str:
32
+ return ''.join(chr(ord(c) ^ (key & 0xFF)) for c in data)
33
+
34
+ # Fonction pour enregistrer les données dans un fichier JSON avec le mot de passe chiffré
35
+ def enregistrer_config(agent, my_domain, password, interval_second):
36
+ try:
37
+ dossier=MetManagement.get_secret_content_dir()
38
+ # Crée le dossier s'il n'existe pas
39
+ if not os.path.exists(dossier):
40
+ os.makedirs(dossier)
41
+
42
+ # Récupère l'adresse MAC et chiffre le mot de passe
43
+ key = get_user_key()
44
+ mdp_chiffre = xor_crypt(password, key)
45
+
46
+ # Nom du fichier (remplace @ par _at_ pour éviter les problèmes)
47
+ nom_fichier = os.path.join(dossier, f"{agent}{my_domain.replace('@', '_at_')}.json")
48
+
49
+ # Contenu à écrire dans le fichier
50
+ contenu = {
51
+ "agent": agent,
52
+ "domain": my_domain,
53
+ "interval_second": interval_second,
54
+ "password_encrypted": mdp_chiffre
55
+ }
56
+
57
+ # Écriture du fichier
58
+ with open(nom_fichier, "w", encoding="utf-8") as f:
59
+ json.dump(contenu, f, indent=4)
60
+
61
+ print(f"✅ Fichier enregistré : {nom_fichier}")
62
+ return 0
63
+
64
+ except Exception as e:
65
+ print(f"❌ Erreur lors de l'enregistrement : {e}")
66
+ return 1
67
+
68
+ # Fonction pour lire le fichier de configuration et déchiffrer le mot de passe
69
+ def lire_config(chemin_fichier):
70
+ # renvoie une liste =["agent","domain",mdp,"interval_second"]
71
+ try:
72
+ chemin_fichier=MetManagement.get_secret_content_dir()+chemin_fichier
73
+ # Lecture du fichier JSON
74
+ with open(chemin_fichier, "r", encoding="utf-8") as f:
75
+ contenu = json.load(f)
76
+
77
+ # Récupère l'adresse MAC
78
+ key = get_user_key()
79
+
80
+ # Déchiffre le mot de passe
81
+ mdp_dechiffre = xor_crypt(contenu["password_encrypted"], key)
82
+
83
+
84
+ return [
85
+ contenu["agent"],
86
+ contenu["domain"],
87
+ mdp_dechiffre,
88
+ int(contenu["interval_second"]),
89
+ ]
90
+
91
+
92
+ except Exception as e:
93
+ print(f"❌ Erreur lors de la lecture : {e}")
94
+ return None
95
+ def enregistrer_config_cli():
96
+ print("\n📝 Écriture d’un fichier de configuration :")
97
+ agent = input("🤖 Nom de l’agent : ").strip()
98
+ domaine = input("📨 @domain.com? : ").strip()
99
+ mdp = input("📨mot de passe? : ").strip()
100
+ interval = int(input("⏱️ Intervalle en secondes : ").strip())
101
+ if 0!= enregistrer_config(agent,domaine,mdp,interval):
102
+ print("erreur!")
103
+
104
+
105
+ def lire_config_cli():
106
+ chemin_fichier = input("📄 non fichier json (pas le chemin!) JSON : ").strip()
107
+ config = lire_config(chemin_fichier)
108
+
109
+ if config==None:
110
+ print("erreur")
111
+ print(config)
112
+
113
+
114
+
115
+ if __name__ == "__main__":
116
+ print("1) ecrire fichier")
117
+ print("2) dechiffer fichier")
118
+ choix = input("👉 que faire? [1/2] : ").strip()
119
+
120
+ if choix == "1":
121
+ enregistrer_config_cli()
122
+ elif choix == "2":
123
+ lire_config_cli()
124
+ else:
125
+ print("❌ Choix invalide. Réessayez.\n")
126
+
@@ -0,0 +1,208 @@
1
+ import os
2
+ import sys
3
+ # import time
4
+ import requests
5
+ # import orangecanvas.application.canvasmain as canvasmain
6
+ import Orange.data
7
+ from AnyQt.QtWidgets import QApplication, QLabel
8
+ from Orange.widgets import widget # , gui
9
+ from Orange.widgets.utils.signals import Input, Output
10
+ # from Orange.widgets.settings import Setting
11
+ from AnyQt.QtWidgets import QLineEdit, QTextBrowser #, QTextEdit
12
+
13
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
14
+ # from Orange.widgets.orangecontrib.AAIT.llm import answers
15
+ from Orange.widgets.orangecontrib.AAIT.utils import thread_management
16
+ from Orange.widgets.orangecontrib.AAIT.utils.import_uic import uic
17
+ from Orange.widgets.orangecontrib.AAIT.utils.initialize_from_ini import apply_modification_from_python_file
18
+ else:
19
+ # from orangecontrib.AAIT.llm import answers
20
+ from orangecontrib.AAIT.utils import thread_management
21
+ from orangecontrib.AAIT.utils.import_uic import uic
22
+ from orangecontrib.AAIT.utils.initialize_from_ini import apply_modification_from_python_file
23
+
24
+ @apply_modification_from_python_file(filepath_original_widget=__file__)
25
+ class OWDeep_Search(widget.OWWidget):
26
+ name = "Deep Search"
27
+ description = "Generate a response to a column 'prompt' with a LLM"
28
+ icon = "icons/deepsearch.svg"
29
+ if "site-packages/Orange/widgets" in os.path.dirname(os.path.abspath(__file__)).replace("\\", "/"):
30
+ icon = "icons_dev/owqueryllm.svg"
31
+ gui = os.path.join(os.path.dirname(os.path.abspath(__file__)), "designer/owdeepsearch.ui")
32
+ want_control_area = True
33
+ priority = 1089
34
+
35
+ class Inputs:
36
+ api_key = Input("API KEY", Orange.data.Table)
37
+ prompt = Input("Prompt", Orange.data.Table)
38
+
39
+ class Outputs:
40
+ response = Output("Response", Orange.data.Table)
41
+
42
+ @Inputs.api_key
43
+ def set_api_key(self, in_api_key: Orange.data.Table):
44
+ if in_api_key is not None:
45
+ if "api_key" not in in_api_key.domain:
46
+ self.error("You need a column api_key");
47
+ return
48
+ self.api_key = str(in_api_key[0]["api_key"]) # ← cast en str
49
+ self.line_api_key.setText(self.api_key)
50
+ self.error(None)
51
+ if self.autorun:
52
+ self.run()
53
+
54
+
55
+ @Inputs.prompt
56
+ def set_prompt(self, in_prompt: Orange.data.Table):
57
+ if in_prompt is None:
58
+ self.Outputs.response.send(None)
59
+ return
60
+ if in_prompt is not None:
61
+ if "prompt" not in in_prompt.domain:
62
+ self.error("You need a column prompt");
63
+ return
64
+ self.prompt = str(in_prompt[0]["prompt"]) # ← cast en str
65
+ self.error(None)
66
+ if self.autorun:
67
+ self.run()
68
+
69
+ def __init__(self):
70
+ super().__init__()
71
+ # Qt Managementt
72
+ self.setFixedWidth(700)
73
+ self.setFixedHeight(500)
74
+ uic.loadUi(self.gui, self)
75
+ self.label_description = self.findChild(QLabel, 'Description')
76
+ # print(self.label_description.text())
77
+ self.line_api_key: QLineEdit = self.findChild(QLineEdit, "line_api_key")
78
+ # Connexion du champ à la fonction de mise à jour
79
+ self.line_api_key.editingFinished.connect(self.update_api_key)
80
+ # Text browser
81
+ self.textBrowser = self.findChild(QTextBrowser, 'textBrowser')
82
+ # Data Management
83
+ self.api_key = None
84
+ self.prompt = None
85
+ self.thread = None
86
+ self.autorun = True
87
+ self.can_run = True
88
+ self.result = None # sera ma table de sortie
89
+ # Custom updates
90
+ self.post_initialized()
91
+
92
+ def update_api_key(self):
93
+ self.api_key = self.line_api_key.text().strip()
94
+
95
+ def deepsearch(self, api_key: str, prompt: str):
96
+ """
97
+ Appelle Perplexity Sonar Deep-Research et renvoie la réponse complète
98
+ dans une Orange Table (métadonnée 'response').
99
+ """
100
+
101
+ url = "https://api.perplexity.ai/chat/completions"
102
+ headers = {
103
+ "Content-Type": "application/json",
104
+ "Authorization": f"Bearer {api_key}"
105
+ }
106
+ payload = {
107
+ "model": "sonar-pro",
108
+ "messages": [
109
+ {"role": "system",
110
+ "content": ("You are a deep research assistant. "
111
+ "Answer only in Markdown without exposing any chain-of-thought.")},
112
+ {"role": "user", "content": prompt}
113
+ ],
114
+ "temperature": 0.0,
115
+ "max_tokens": 20_000,
116
+ "format": "markdown"
117
+ }
118
+
119
+ try:
120
+ resp = requests.post(url, json=payload, headers=headers, timeout=600)
121
+ resp.raise_for_status()
122
+ answer_md = resp.json()["choices"][0]["message"]["content"]
123
+
124
+ except requests.HTTPError: #as e:
125
+ print("STATUS :", resp.status_code) # affiche 400, 401, etc.
126
+ print("DETAIL :", resp.text) # message JSON complet de Perplexity
127
+
128
+ answer_md = f"**Erreur Perplexity :** {resp.text}"
129
+
130
+ except Exception as e:
131
+ answer_md = f"**Erreur Perplexity :** {e}"
132
+
133
+ # Affiche la réponse complète dans la console
134
+ # print("Réponse Perplexity :\n", answer_md)
135
+
136
+ response_var = Orange.data.StringVariable("response")
137
+
138
+ domain = Orange.data.Domain([], metas=[response_var])
139
+ out_data = Orange.data.Table.from_list(domain, [[answer_md]])
140
+ return out_data
141
+
142
+ def run(self):
143
+ # print("I'm running...")
144
+ # print(self.prompt)
145
+ # print(self.api_key)
146
+ if not self.can_run:
147
+ return
148
+
149
+ if self.prompt == None:
150
+ return
151
+
152
+ if self.api_key == None:
153
+ # print("Je sors")
154
+ return
155
+
156
+ # If Thread is already running, interrupt it
157
+ if self.thread is not None:
158
+ if self.thread.isRunning():
159
+ self.thread.safe_quit()
160
+
161
+ # Start progress bar
162
+ self.progressBarInit()
163
+ #◙self.textBrowser.setText("")
164
+
165
+ # Connect and start thread : main function, progress, result and finish
166
+ # --> progress is used in the main function to track progress (with a callback)
167
+ # --> result is used to collect the result from main function
168
+ # --> finish is just an empty signal to indicate that the thread is finished
169
+ self.thread = thread_management.Thread(self.deepsearch, self.api_key, self.prompt)
170
+ self.thread.progress.connect(self.handle_progress)
171
+ self.thread.result.connect(self.handle_result)
172
+ self.thread.finish.connect(self.handle_finish)
173
+ self.thread.start()
174
+
175
+ def handle_progress(self, progress) -> None:
176
+ value = progress[0]
177
+ text = progress[1]
178
+ if value is not None:
179
+ self.progressBarSet(value)
180
+ if text is None:
181
+ self.textBrowser.setText("")
182
+ else:
183
+ self.textBrowser.insertPlainText(text)
184
+
185
+ def handle_result(self, result):
186
+ try:
187
+ self.result = result
188
+ self.Outputs.response.send(result)
189
+ except Exception as e:
190
+ print("An error occurred when sending out_data:", e)
191
+ self.Outputs.response.send(None)
192
+ return
193
+
194
+ def handle_finish(self):
195
+ print("Generation finished")
196
+ self.progressBarFinished()
197
+
198
+
199
+ def post_initialized(self):
200
+ pass
201
+
202
+
203
+ if __name__ == "__main__":
204
+ app = QApplication(sys.argv)
205
+ my_widget = OWDeep_Search()
206
+ my_widget.show()
207
+ app.exec_()
208
+
@@ -54,6 +54,7 @@ class OWS3FileDownloader(OWWidget):
54
54
  self.access_key = self.access_key_input.text()
55
55
  self.secret_key = self.secret_key_input.text()
56
56
  self.bucket_name = self.bucket_input.text()
57
+ self.upload_files()
57
58
 
58
59
  def select_directory(self):
59
60
  directory = QFileDialog.getExistingDirectory(self, "Sélectionner un répertoire")
@@ -63,8 +64,14 @@ class OWS3FileDownloader(OWWidget):
63
64
  @Inputs.data
64
65
  def set_data(self, data):
65
66
  self.data = data
67
+ if self.data is not None and len(self.data.domain) > 0:
68
+ if "folder_path" in self.data.domain:
69
+ self.download_path = self.data.get_column("folder_path")[0]
70
+ self.upload_files()
66
71
 
67
72
  def upload_files(self):
73
+ self.error("")
74
+ self.information("")
68
75
  if self.data is None:
69
76
  self.error("Aucune donnée reçue.")
70
77
  return
@@ -0,0 +1,74 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ui version="4.0">
3
+ <class>Form</class>
4
+ <widget class="QWidget" name="Form">
5
+ <property name="geometry">
6
+ <rect>
7
+ <x>0</x>
8
+ <y>0</y>
9
+ <width>700</width>
10
+ <height>500</height>
11
+ </rect>
12
+ </property>
13
+ <property name="windowTitle">
14
+ <string>Form</string>
15
+ </property>
16
+ <widget class="QLabel" name="label">
17
+ <property name="geometry">
18
+ <rect>
19
+ <x>10</x>
20
+ <y>70</y>
21
+ <width>121</width>
22
+ <height>21</height>
23
+ </rect>
24
+ </property>
25
+ <property name="text">
26
+ <string>API Key :</string>
27
+ </property>
28
+ </widget>
29
+ <widget class="QLabel" name="Description">
30
+ <property name="geometry">
31
+ <rect>
32
+ <x>30</x>
33
+ <y>10</y>
34
+ <width>551</width>
35
+ <height>51</height>
36
+ </rect>
37
+ </property>
38
+ <property name="text">
39
+ <string>This widget generates a deepsearch answer on the column &quot;prompt&quot; of your input data.</string>
40
+ </property>
41
+ <property name="textFormat">
42
+ <enum>Qt::AutoText</enum>
43
+ </property>
44
+ <property name="alignment">
45
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
46
+ </property>
47
+ <property name="wordWrap">
48
+ <bool>true</bool>
49
+ </property>
50
+ </widget>
51
+ <widget class="QTextBrowser" name="textBrowser">
52
+ <property name="geometry">
53
+ <rect>
54
+ <x>20</x>
55
+ <y>120</y>
56
+ <width>661</width>
57
+ <height>331</height>
58
+ </rect>
59
+ </property>
60
+ </widget>
61
+ <widget class="QLineEdit" name="line_api_key">
62
+ <property name="geometry">
63
+ <rect>
64
+ <x>100</x>
65
+ <y>60</y>
66
+ <width>571</width>
67
+ <height>41</height>
68
+ </rect>
69
+ </property>
70
+ </widget>
71
+ </widget>
72
+ <resources/>
73
+ <connections/>
74
+ </ui>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="800px" height="800px"
2
+ fill="none" stroke="#000" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
3
+ <!-- Pile de documents -->
4
+ <rect x="6" y="8" width="14" height="18" rx="1"/>
5
+ <rect x="10" y="12" width="14" height="18" rx="1"/>
6
+ <rect x="14" y="16" width="14" height="18" rx="1"/>
7
+ <!-- Loupe -->
8
+ <circle cx="24" cy="24" r="7"/>
9
+ <line x1="28" y1="28" x2="32" y2="32"/>
10
+ </svg>
@@ -36,7 +36,7 @@ class CustomInstall(install):
36
36
 
37
37
  # Configuration
38
38
  NAME = "io4it"
39
- VERSION = "1.0.1.1"
39
+ VERSION = "1.0.3"
40
40
 
41
41
  INSTALL_REQUIRES = [
42
42
  "boto3",
File without changes
File without changes