Nouvelle couche app/ai/ (provider OpenAI-compatible via urllib, redaction de secrets, detection de commandes dangereuses, parametres, worker QThread) et son integration dans chaque onglet terminal via un bouton "Assistant IA" (memes conventions que le bouton Snippets existant) : - Expliquer cette sortie / Diagnostiquer cette erreur / Proposer une solution / Poser une question... : utilisent la selection courante du terminal, affichees dans un panneau lateral (memoire uniquement, jamais journalise) reutilisant le splitter comme le panneau SFTP. - Generer une commande : ne s'execute jamais automatiquement (Copier / Inserer dans le terminal sans Entree / Annuler), avec avertissement si la commande proposee correspond a un motif destructeur connu. - Avant tout envoi : avis unique (persist) puis detection de secrets evidents (mot de passe, cle, jeton) avec choix de masquer, envoyer tel quel ou annuler. Desactive par defaut, sans liste de fournisseur figee (URL + modele libres, compatible OpenAI/OpenRouter/LLM local). La cle API passe exclusivement par CredentialStore (jamais dans settings.json ni journalisee). Parametrable via le nouveau menu "Assistant IA > Parametres..." (bouton "Tester la connexion"). Bug trouve par les tests avant commit : un panneau nouvellement insere dans un QSplitter deja visible est affiche automatiquement par Qt, ce qui inversait le premier clic sur "Afficher le panneau IA" ; corrige en le masquant explicitement a sa creation. 9 nouveaux fichiers de test (provider, redaction, dangers, parametres, worker, confirmations de securite, dialogue de commande, panneau, integration onglet) : 39 verifications, toutes au vert. Suite complete (31 fichiers) executee sans regression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
71 lines
2.8 KiB
Python
71 lines
2.8 KiB
Python
"""Confirmations de securite avant tout envoi de contenu au fournisseur IA :
|
|
un avertissement de principe affiche une seule fois, puis une verification
|
|
systematique des secrets evidents a chaque envoi d'une selection de terminal
|
|
ou d'une demande libre."""
|
|
from __future__ import annotations
|
|
|
|
from PySide6.QtWidgets import QMessageBox
|
|
|
|
from app.app_settings import get_setting, set_setting
|
|
from app.ai.redaction import detect_secrets
|
|
|
|
_NOTICE_ACK_KEY = "ai_notice_acknowledged"
|
|
|
|
MASK = "mask"
|
|
SEND = "send"
|
|
CANCEL = "cancel"
|
|
|
|
|
|
def ensure_ai_notice_acknowledged(parent) -> bool:
|
|
"""Affiche l'avertissement de principe une seule fois (jamais reaffiche
|
|
ensuite, l'accord etant persiste). Renvoie False si l'utilisateur refuse :
|
|
l'appelant ne doit alors rien envoyer."""
|
|
if get_setting(_NOTICE_ACK_KEY, False):
|
|
return True
|
|
box = QMessageBox(parent)
|
|
box.setWindowTitle("Assistant IA")
|
|
box.setIcon(QMessageBox.Information)
|
|
box.setText(
|
|
"Le texte selectionne dans le terminal (ou votre question) va etre envoye "
|
|
"au fournisseur IA que vous avez configure, en dehors de cet ordinateur.\n\n"
|
|
"PySSH tente de detecter les secrets evidents (mots de passe, cles, jetons) "
|
|
"avant chaque envoi et vous permet de les masquer, mais ne peut pas tout "
|
|
"detecter : verifiez ce que vous envoyez.\n\n"
|
|
"Ce message ne sera plus affiche."
|
|
)
|
|
box.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
|
|
box.setDefaultButton(QMessageBox.Ok)
|
|
accepted = box.exec() == QMessageBox.Ok
|
|
if accepted:
|
|
set_setting(_NOTICE_ACK_KEY, True)
|
|
return accepted
|
|
|
|
|
|
def confirm_sensitive_content(parent, text: str) -> str:
|
|
"""Renvoie MASK, SEND ou CANCEL. Si aucun secret evident n'est detecte,
|
|
renvoie SEND directement sans rien afficher."""
|
|
secrets = detect_secrets(text)
|
|
if not secrets:
|
|
return SEND
|
|
labels = "\n".join(f"- {s.label}" for s in secrets)
|
|
box = QMessageBox(parent)
|
|
box.setWindowTitle("Donnees potentiellement sensibles")
|
|
box.setIcon(QMessageBox.Warning)
|
|
box.setText(
|
|
"Des donnees potentiellement sensibles ont ete detectees dans le texte "
|
|
f"qui serait envoye au fournisseur IA :\n\n{labels}\n\n"
|
|
"« Masquer automatiquement » les remplace par [MASQUE] avant l'envoi.\n"
|
|
"« Voir le contenu » l'envoie tel quel, sans modification."
|
|
)
|
|
mask_btn = box.addButton("Masquer automatiquement", QMessageBox.AcceptRole)
|
|
send_btn = box.addButton("Voir le contenu", QMessageBox.DestructiveRole)
|
|
box.addButton("Annuler", QMessageBox.RejectRole)
|
|
box.setDefaultButton(mask_btn)
|
|
box.exec()
|
|
clicked = box.clickedButton()
|
|
if clicked is mask_btn:
|
|
return MASK
|
|
if clicked is send_btn:
|
|
return SEND
|
|
return CANCEL
|