diff --git a/app.py b/app.py index 3c515c8..726b3bb 100644 --- a/app.py +++ b/app.py @@ -46,6 +46,10 @@ CONFIG_DEFAUT = { "Bien cordialement," ), "ia_modele": ia.MODELE_DEFAUT, + "groupe_nom": "", + "groupe_style": "", + "groupe_description": "", + "groupe_lien": "", } app = Flask(__name__, static_folder="static") @@ -368,7 +372,8 @@ def api_config_lire(): def api_config_ecrire(): donnees = request.get_json(silent=True) or {} config = lire_config() - for cle in ("adresse_depart", "modele_message", "ia_modele"): + for cle in ("adresse_depart", "modele_message", "ia_modele", + "groupe_nom", "groupe_style", "groupe_description", "groupe_lien"): if cle in donnees: config[cle] = str(donnees[cle]) for cle in ("conso_l_100km", "prix_carburant", "cout_peage_km"): @@ -411,6 +416,12 @@ def api_message(): message = ia.generer_message( prospect, config.get("modele_message", ""), mode, nom_modele=config.get("ia_modele") or ia.MODELE_DEFAUT, + groupe={ + "nom": config.get("groupe_nom", ""), + "style": config.get("groupe_style", ""), + "description": config.get("groupe_description", ""), + "lien": config.get("groupe_lien", ""), + }, ) return jsonify({"message": message}) except ia.IANonConnecte as e: diff --git a/ia.py b/ia.py index 4259355..14df0f4 100644 --- a/ia.py +++ b/ia.py @@ -127,6 +127,25 @@ def _infos_prospect(prospect): return "\n".join(f"- {cle} : {val}" for cle, val in champs if (val or "").strip()) +def _infos_groupe(groupe): + """Bloc décrivant le groupe (depuis les réglages), injecté dans le prompt système. + + Ne liste que les champs renseignés ; renvoie "" si rien n'est fourni. + """ + if not groupe: + return "" + champs = [ + ("Nom du groupe", groupe.get("nom")), + ("Style", groupe.get("style")), + ("Description", groupe.get("description")), + ("Lien", groupe.get("lien")), + ] + lignes = [f"- {cle} : {(val or '').strip()}" for cle, val in champs if (val or "").strip()] + if not lignes: + return "" + return "Tu représentes le groupe suivant :\n" + "\n".join(lignes) + + def _message_erreur(exc): bas = str(exc).lower() if "rate" in bas or "429" in bas or "limit" in bas or "quota" in bas: @@ -136,12 +155,15 @@ def _message_erreur(exc): return f"Échec de la génération : {exc}" -def generer_message(prospect, modele, mode="generer", nom_modele=None): +def generer_message(prospect, modele, mode="generer", nom_modele=None, groupe=None): """Génère un message de prise de contact pour un prospect. mode == "generer" : l'IA rédige un message sur mesure (modèle = guide de ton/style). mode == "peaufiner" : on substitue d'abord le modèle, puis l'IA le reformule sans en changer le sens. + + `groupe` : dict optionnel (nom, style, description, lien) issu des réglages ; ses champs + renseignés sont injectés dans le prompt système pour personnaliser la rédaction. """ nom_modele = nom_modele or MODELE_DEFAUT if not est_connecte(): @@ -150,25 +172,36 @@ def generer_message(prospect, modele, mode="generer", nom_modele=None): ) infos = _infos_prospect(prospect) or "- (aucune information détaillée disponible)" + bloc_groupe = _infos_groupe(groupe) if mode == "peaufiner": brouillon = _message_modele(prospect, modele) - systeme = ( + parties = [ "Tu rédiges des messages de prise de contact en français pour proposer " - "l'organisation de concerts à des établissements. On te donne un brouillon : " - "reformule-le pour le rendre plus naturel, chaleureux et engageant, sans inventer " - "d'information ni changer le sens. Réponds UNIQUEMENT par le message final, sans " - "commentaire." + "l'organisation de concerts à des établissements." + ] + if bloc_groupe: + parties.append(bloc_groupe) + parties.append( + "On te donne un brouillon : reformule-le pour le rendre plus naturel, chaleureux " + "et engageant, sans inventer d'information ni changer le sens. Réponds UNIQUEMENT " + "par le message final, sans commentaire." ) + systeme = "\n\n".join(parties) utilisateur = f"Brouillon à améliorer :\n{brouillon}\n\nInfos sur l'établissement :\n{infos}" else: - systeme = ( + parties = [ "Tu rédiges des messages de prise de contact en français pour proposer " - "l'organisation de concerts à des établissements (bars, restaurants, salles...). " + "l'organisation de concerts à des établissements (bars, restaurants, salles...)." + ] + if bloc_groupe: + parties.append(bloc_groupe) + parties.append( "Le message doit être court, personnalisé, chaleureux et se terminer par une " "question ouvrant l'échange. Réponds UNIQUEMENT par le message final, sans " "commentaire ni objet d'e-mail." ) + systeme = "\n\n".join(parties) utilisateur = ( f"Rédige un message de prise de contact pour cet établissement :\n{infos}\n\n" f"Inspire-toi de ce modèle pour le ton et le style :\n{modele}" diff --git a/static/index.html b/static/index.html index 8a6eca8..873c0ad 100644 --- a/static/index.html +++ b/static/index.html @@ -136,6 +136,23 @@ .spinner.sombre { border-color: var(--accent); border-top-color: transparent; } @keyframes tourne { to { transform: rotate(360deg); } } + /* ===== Disposition « modale » : liste en grand + détail en fenêtre centrale ===== */ + body[data-disposition="modale"] .deux-colonnes { grid-template-columns: 1fr; } + body[data-disposition="modale"] .colonne-detail { display: none; } + body[data-disposition="modale"] #liste { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--e3); } + body[data-disposition="modale"] .item { margin-bottom: 0; padding: var(--e3) var(--e4); } + body.modale-ouverte { overflow: hidden; } + + .modale-fond { position: fixed; inset: 0; z-index: 50; background: rgba(29,27,46,.45); display: flex; align-items: flex-start; justify-content: center; padding: var(--e8) var(--e4); overflow-y: auto; } + .modale-fond[hidden] { display: none; } + .modale { position: relative; width: 100%; max-width: 720px; background: var(--fond); border: 1px solid var(--bordure); border-radius: var(--r-m); box-shadow: 0 12px 48px rgba(29,27,46,.3); padding: var(--e6); } + .modale-fermer { position: absolute; top: var(--e3); right: var(--e3); width: 32px; height: 32px; padding: 0; font-size: 16px; line-height: 1; background: var(--accent-doux); color: var(--accent); } + .modale-fermer:hover { background: #e3e0f6; } + @media (max-width: 640px) { + .modale { padding: var(--e4); } + .modale-fond { padding: var(--e4) var(--e2); } + } + @media (max-width: 860px) { .deux-colonnes { grid-template-columns: 1fr; } .colonne-detail { position: static; max-height: none; } @@ -228,6 +245,18 @@