diff --git "a/refApp.py" "b/refApp.py" --- "a/refApp.py" +++ "b/refApp.py" @@ -3,10 +3,17 @@ from datetime import datetime, date import copy import json import requests -import os + +hide_streamlit_style = """ + +""" +st.markdown(hide_streamlit_style, unsafe_allow_html=True) # --- PAGE CONFIGURATION (MUST BE THE FIRST STREAMLIT COMMAND) --- -# Fixed deployment issue st.set_page_config(layout="wide", page_title="🛠️ Le laboratoire des Prompts IA", initial_sidebar_state="collapsed" ) # --- CUSTOM CSS FOR SIDEBAR TOGGLE TEXT --- @@ -38,8 +45,8 @@ st.markdown(""" /* Cible le div conteneur direct à l'intérieur de stCodeBlock s'il existe et gère le scroll */ div[data-testid="stCodeBlock"] > div:first-child { - /* height: 120px !important; - SUPPRIMÉ : causait des problèmes de layout */ - max-height: 520px !important; /* Correspond à la valeur du pre ci-dessus */ + height: 120px !important; /* Doit correspondre à la valeur ci-dessus */ + max-height: 120px !important; overflow-y: auto !important; display: block !important; visibility: visible !important; @@ -79,23 +86,15 @@ st.markdown(""" /* === SOLUTION POUR COMPRESSION LATERALE DE LA SIDEBAR === */ /* Force le contenu principal à se comprimer au lieu d'être décalé */ section[data-testid="stSidebar"] { - width: 31.5rem !important; - min-width: 31.5rem !important; - max-width: 31.5rem !important; - } - - /* RÉDUIRE LA SIDEBAR QUAND FERMÉE */ - section[data-testid="stSidebar"][aria-expanded="false"] { - width: 0rem !important; - min-width: 0rem !important; - max-width: 0rem !important; - overflow: hidden !important; + width: 21rem !important; + min-width: 21rem !important; + max-width: 21rem !important; } /* Ajustement du conteneur principal pour la compression */ .main .block-container { - max-width: calc(100vw - 31.5rem) !important; - width: calc(100vw - 31.5rem) !important; + max-width: calc(100vw - 21rem) !important; + width: calc(100vw - 21rem) !important; } /* Quand la sidebar est fermée, reprendre toute la largeur */ @@ -110,26 +109,6 @@ st.markdown(""" transition: width 0.3s ease, max-width 0.3s ease !important; } - /* PADDING SPECIFIQUE POUR INTERPRO1_LIGHT */ - [data-testid="stMainBlockContainer"] { - padding-top: 3rem !important; - } - - .css-1d391kg, .css-18e3th9 { - padding-top: 3rem !important; - } - - /* REDUCTION SPECIFIQUE POUR LA PAGE D'ACCUEIL UNIQUEMENT */ - h1[data-testid="stHeading"]:first-of-type { - margin-top: -2rem !important; - padding-top: 0rem !important; - } - - h1:contains("Bienvenue dans votre laboratoire") { - margin-top: -2rem !important; - padding-top: 0rem !important; - } - /* Responsive: sur petits écrans, garder le comportement normal */ @media (max-width: 768px) { .main .block-container { @@ -138,172 +117,12 @@ st.markdown(""" } } - /* === RÈGLES SPÉCIFIQUES POUR st.code() QUI CAUSE LE PROBLÈME === */ - /* Forcer la compression sur TOUS les blocs de code */ - [data-testid="stCodeBlock"], - div[data-testid="stCodeBlock"], - .stCodeBlock { - max-width: 100vw !important; - width: 100vw !important; - box-sizing: border-box !important; - } - - /* Quand sidebar ouverte, compresser les blocs de code */ - section[data-testid="stSidebar"][aria-expanded="true"] ~ .main [data-testid="stCodeBlock"], - section[data-testid="stSidebar"][aria-expanded="true"] ~ .main div[data-testid="stCodeBlock"], - section[data-testid="stSidebar"][aria-expanded="true"] ~ .main .stCodeBlock { - max-width: calc(100vw - 31.5rem) !important; - width: calc(100vw - 31.5rem) !important; - } - - /* Forcer sur les éléments internes du code block */ - [data-testid="stCodeBlock"] > div, - [data-testid="stCodeBlock"] pre, - [data-testid="stCodeBlock"] code { - max-width: 100% !important; - width: 100% !important; - box-sizing: border-box !important; - overflow-x: auto !important; - } - - /* Quand sidebar ouverte, aussi sur les éléments internes */ - section[data-testid="stSidebar"][aria-expanded="true"] ~ .main [data-testid="stCodeBlock"] > div, - section[data-testid="stSidebar"][aria-expanded="true"] ~ .main [data-testid="stCodeBlock"] pre, - section[data-testid="stSidebar"][aria-expanded="true"] ~ .main [data-testid="stCodeBlock"] code { - max-width: calc(100vw - 31.5rem) !important; - width: calc(100vw - 31.5rem) !important; - } - - - + - """, unsafe_allow_html=True) - - # No vertical spacing to align at top - - # Create the access form - with st.container(): - st.markdown(""" -
-

🔐 Accès Sécurisé

-

- Veuillez entrer le code d'accès pour utiliser l'application -

-
- """, unsafe_allow_html=True) - - # Center the form elements - col1, col2, col3 = st.columns([1, 2, 1]) - with col2: - access_code = st.text_input( - "Code d'accès", - placeholder="Entrez le code d'accès...", - key="access_code_input_field" - ) - - if st.button("✅ Valider", use_container_width=True, type="primary"): - if access_code == "SUPPORT": - st.session_state.access_granted = True - st.rerun() - else: - st.error("❌ Code d'accès incorrect") - - st.markdown(""" -

- Pour demander un accès, contactez votre référent métier ou l'administrateur de l'application : - arthur.causse-prestataire@laposte.fr -

- """, unsafe_allow_html=True) - - # Stop execution here if access not granted - st.stop() - # --- Session State Initialization --- if 'editable_prompts' not in st.session_state: - st.session_state.editable_prompts = load_editable_prompts_from_local() + st.session_state.editable_prompts = load_editable_prompts_from_gist() if 'view_mode' not in st.session_state: st.session_state.view_mode = "accueil" # Nouvelle vue par défaut @@ -665,18 +444,11 @@ if 'confirming_delete_details' not in st.session_state: st.session_state.confirm if 'confirming_delete_family_name' not in st.session_state: st.session_state.confirming_delete_family_name = None if 'library_search_term' not in st.session_state: st.session_state.library_search_term = "" if 'library_selected_tags' not in st.session_state: st.session_state.library_selected_tags = [] -# Track previous filter values for change detection -if 'previous_search_term' not in st.session_state: st.session_state.previous_search_term = "" -if 'previous_selected_tags' not in st.session_state: st.session_state.previous_selected_tags = [] if 'variable_type_to_create' not in st.session_state: st.session_state.variable_type_to_create = None if 'active_generated_prompt' not in st.session_state: st.session_state.active_generated_prompt = "" if 'duplicating_use_case_details' not in st.session_state: st.session_state.duplicating_use_case_details = None if 'go_to_config_section' not in st.session_state: st.session_state.go_to_config_section = False -# Generator session state variables -if 'generator_selected_family' not in st.session_state: st.session_state.generator_selected_family = None -if 'generator_selected_use_case' not in st.session_state: st.session_state.generator_selected_use_case = None - if 'injection_selected_family' not in st.session_state: st.session_state.injection_selected_family = None if 'injection_json_text' not in st.session_state: @@ -695,11 +467,235 @@ if 'assistant_existing_prompt_value' not in st.session_state: # --- Sidebar Navigation with Tabs --- st.sidebar.header("Menu Principal") -tab_bibliotheque, tab_injection = st.sidebar.tabs([ +tab_bibliotheque, tab_edition_generation, tab_injection = st.sidebar.tabs([ "📚 Bibliothèque", + "✍️ Édition", "💡 Assistant" ]) +# --- Tab: Édition (Sidebar content) --- +with tab_edition_generation: + st.subheader("Explorateur de Prompts") + available_families = list(st.session_state.editable_prompts.keys()) + default_family_idx_edit = 0 + current_family_for_edit = st.session_state.get('family_selector_edition') + + if st.session_state.force_select_family_name and st.session_state.force_select_family_name in available_families: + current_family_for_edit = st.session_state.force_select_family_name + st.session_state.family_selector_edition = current_family_for_edit + elif current_family_for_edit and current_family_for_edit in available_families: + pass + elif available_families: + current_family_for_edit = available_families[0] + st.session_state.family_selector_edition = current_family_for_edit + else: + current_family_for_edit = None + st.session_state.family_selector_edition = None + + if current_family_for_edit and current_family_for_edit in available_families: + default_family_idx_edit = available_families.index(current_family_for_edit) + elif available_families: + default_family_idx_edit = 0 + current_family_for_edit = available_families[0] + st.session_state.family_selector_edition = current_family_for_edit + else: + default_family_idx_edit = 0 + + if not available_families: + st.info("Aucune famille de métier de cas d'usage. Créez-en une via les options ci-dessous.") + else: + prev_family_selection_edit = st.session_state.get('family_selector_edition') + selected_family_ui_edit = st.selectbox( + "Métier :", + options=available_families, + index=default_family_idx_edit, + key='family_selectbox_widget_edit', + help="Sélectionnez une métier pour voir ses cas d'usage." + ) + if st.session_state.family_selector_edition != selected_family_ui_edit : + st.session_state.family_selector_edition = selected_family_ui_edit + + if prev_family_selection_edit != selected_family_ui_edit: + st.session_state.use_case_selector_edition = None + st.session_state.force_select_use_case_name = None + st.session_state.view_mode = "edit" + st.session_state.active_generated_prompt = "" + st.session_state.variable_type_to_create = None + st.session_state.editing_variable_info = None + st.rerun() + + current_selected_family_for_edit_logic = st.session_state.get('family_selector_edition') + use_cases_in_current_family_edit_options = [] + if current_selected_family_for_edit_logic and current_selected_family_for_edit_logic in st.session_state.editable_prompts: + use_cases_in_current_family_edit_options = list(st.session_state.editable_prompts[current_selected_family_for_edit_logic].keys()) + + if use_cases_in_current_family_edit_options: + default_uc_idx_edit = 0 + current_uc_for_edit = st.session_state.get('use_case_selector_edition') + + if st.session_state.force_select_use_case_name and st.session_state.force_select_use_case_name in use_cases_in_current_family_edit_options: + current_uc_for_edit = st.session_state.force_select_use_case_name + elif current_uc_for_edit and current_uc_for_edit in use_cases_in_current_family_edit_options: + pass + else: + current_uc_for_edit = use_cases_in_current_family_edit_options[0] + + st.session_state.use_case_selector_edition = current_uc_for_edit + + if current_uc_for_edit: + default_uc_idx_edit = use_cases_in_current_family_edit_options.index(current_uc_for_edit) + + prev_uc_selection_edit = st.session_state.get('use_case_selector_edition') + selected_use_case_ui_edit = st.radio( + "Cas d'usage :", + options=use_cases_in_current_family_edit_options, + index=default_uc_idx_edit, + key='use_case_radio_widget_edit', + help="Sélectionnez un cas d'usage pour générer un prompt ou le paramétrer." + ) + if st.session_state.use_case_selector_edition != selected_use_case_ui_edit: + st.session_state.use_case_selector_edition = selected_use_case_ui_edit + + if prev_uc_selection_edit != selected_use_case_ui_edit: + st.session_state.view_mode = "edit" + st.session_state.active_generated_prompt = "" + st.session_state.variable_type_to_create = None + st.session_state.editing_variable_info = None + st.rerun() + + elif current_selected_family_for_edit_logic: + st.info(f"Aucun cas d'usage dans '{current_selected_family_for_edit_logic}'. Créez-en un.") + st.session_state.use_case_selector_edition = None + + if st.session_state.force_select_family_name: st.session_state.force_select_family_name = None + if st.session_state.force_select_use_case_name: st.session_state.force_select_use_case_name = None + st.markdown("---") + + with st.expander("🗂️ Gérer les familles de prompts par métier", expanded=False): + with st.form("new_family_form_sidebar", clear_on_submit=True): + new_family_name = st.text_input("Nom du nouveau métier:", key="new_fam_name_sidebar") + submitted_new_family = st.form_submit_button("➕ Créer métier") + if submitted_new_family and new_family_name.strip(): + if new_family_name.strip() in st.session_state.editable_prompts: + st.error(f"Le métier '{new_family_name.strip()}' existe déjà.") + else: + st.session_state.editable_prompts[new_family_name.strip()] = {} + save_editable_prompts_to_gist() + st.success(f"Métier '{new_family_name.strip()}' créée.") + st.session_state.force_select_family_name = new_family_name.strip() + st.session_state.use_case_selector_edition = None + st.session_state.view_mode = "edit" + st.rerun() + elif submitted_new_family: + st.error("Le nom du métier ne peut pas être vide.") + + if available_families and current_selected_family_for_edit_logic : + st.markdown("---") + with st.form("rename_family_form_sidebar"): + st.write(f"Renommer le métier : **{current_selected_family_for_edit_logic}**") + renamed_family_name_input = st.text_input("Nouveau nom :", value=current_selected_family_for_edit_logic, key="ren_fam_name_sidebar") + submitted_rename_family = st.form_submit_button("✏️ Renommer") + if submitted_rename_family and renamed_family_name_input.strip(): + renamed_family_name = renamed_family_name_input.strip() + if renamed_family_name == current_selected_family_for_edit_logic: + st.info("Le nouveau nom est identique à l'ancien.") + elif renamed_family_name in st.session_state.editable_prompts: + st.error(f"Un métier nommé '{renamed_family_name}' existe déjà.") + else: + st.session_state.editable_prompts[renamed_family_name] = st.session_state.editable_prompts.pop(current_selected_family_for_edit_logic) + save_editable_prompts_to_gist() + st.success(f"Métier '{current_selected_family_for_edit_logic}' renommé en '{renamed_family_name}'.") + st.session_state.force_select_family_name = renamed_family_name + if st.session_state.library_selected_family_for_display == current_selected_family_for_edit_logic: + st.session_state.library_selected_family_for_display = renamed_family_name + st.session_state.view_mode = "edit" + st.rerun() + elif submitted_rename_family: + st.error("Le nouveau nom du métier ne peut pas être vide.") + + st.markdown("---") + st.write(f"Supprimer le métier : **{current_selected_family_for_edit_logic}**") + if st.session_state.confirming_delete_family_name == current_selected_family_for_edit_logic: + st.warning(f"Supprimer '{current_selected_family_for_edit_logic}' et tous ses cas d'usage ? Action irréversible.") + + _text_confirm_delete = f"Oui, supprimer définitivement '{current_selected_family_for_edit_logic}'" + if st.button(_text_confirm_delete, type="primary", key=f"confirm_del_fam_sb_{current_selected_family_for_edit_logic}", use_container_width=True): + deleted_fam_name = current_selected_family_for_edit_logic + del st.session_state.editable_prompts[current_selected_family_for_edit_logic] + save_editable_prompts_to_gist() + st.success(f"Métier '{deleted_fam_name}' supprimée.") + st.session_state.confirming_delete_family_name = None + st.session_state.family_selector_edition = None + st.session_state.use_case_selector_edition = None + if st.session_state.library_selected_family_for_display == deleted_fam_name: + st.session_state.library_selected_family_for_display = None + st.session_state.view_mode = "edit" + st.rerun() + + if st.button("Non, annuler la suppression", key=f"cancel_del_fam_sb_{current_selected_family_for_edit_logic}", use_container_width=True): + st.session_state.confirming_delete_family_name = None + st.session_state.view_mode = "edit" + st.rerun() + else: + if st.button(f"🗑️ Supprimer le métier Sélectionnée", key=f"del_fam_btn_sb_{current_selected_family_for_edit_logic}"): + st.session_state.confirming_delete_family_name = current_selected_family_for_edit_logic + st.session_state.view_mode = "edit" + st.rerun() + elif not available_families: + st.caption("Créez un métier pour pouvoir le gérer.") + else: + st.caption("Sélectionnez un métier (ci-dessus) pour le gérer.") + + st.markdown("---") + + with st.expander("➕ Créer un Cas d'Usage", expanded=st.session_state.get('show_create_new_use_case_form', False)): + if not available_families: + st.caption("Veuillez d'abord créer une famille de métier pour y ajouter des cas d'usage.") + else: + if st.button("Afficher/Masquer Formulaire de Création de Cas d'Usage", key="toggle_create_uc_form_in_exp"): + st.session_state.show_create_new_use_case_form = not st.session_state.get('show_create_new_use_case_form', False) + st.rerun() + + if st.session_state.get('show_create_new_use_case_form', False): + with st.form("new_use_case_form_in_exp", clear_on_submit=True): + default_create_family_idx_tab = 0 + if current_selected_family_for_edit_logic and current_selected_family_for_edit_logic in available_families: + default_create_family_idx_tab = available_families.index(current_selected_family_for_edit_logic) + + uc_parent_family = st.selectbox( + "Métier parent du nouveau cas d'usage:", + options=available_families, + index=default_create_family_idx_tab, + key="new_uc_parent_fam_in_exp" + ) + uc_name_input = st.text_input("Nom du Nouveau Cas d'Usage:", key="new_uc_name_in_exp") + uc_template_input = st.text_area("Template Initial du Cas d'Usage:", height=100, key="new_uc_template_in_exp", value="Nouveau prompt...") + submitted_new_uc = st.form_submit_button("Créer Cas d'Usage") + + if submitted_new_uc: + parent_family_val = uc_parent_family + uc_name_val = uc_name_input.strip() + uc_template_val = uc_template_input + + if not uc_name_val: + st.error("Le nom du cas d'usage ne peut pas être vide.") + elif uc_name_val in st.session_state.editable_prompts.get(parent_family_val, {}): + st.error(f"Le cas d'usage '{uc_name_val}' existe déjà dans le métier '{parent_family_val}'.") + else: + now_iso_create, now_iso_update = get_default_dates() + st.session_state.editable_prompts[parent_family_val][uc_name_val] = { + "template": uc_template_val or "Nouveau prompt...", + "variables": [], "tags": [], + "usage_count": 0, "created_at": now_iso_create, "updated_at": now_iso_update + } + save_editable_prompts_to_gist() + st.success(f"Cas d'usage '{uc_name_val}' créé avec succès dans '{parent_family_val}'.") + st.session_state.show_create_new_use_case_form = False + st.session_state.force_select_family_name = parent_family_val + st.session_state.force_select_use_case_name = uc_name_val + st.session_state.view_mode = "edit" + st.session_state.active_generated_prompt = "" + st.rerun() # --- Tab: Bibliothèque (Sidebar content) --- with tab_bibliotheque: @@ -719,29 +715,6 @@ with tab_bibliotheque: options=all_tags_list, default=st.session_state.get("library_selected_tags", []) ) - - # Detect filter changes and auto-redirect to global search results - current_search_term = st.session_state.get("library_search_term", "").strip() - current_selected_tags = st.session_state.get("library_selected_tags", []) - - # Check if filters have changed - search_changed = current_search_term != st.session_state.get("previous_search_term", "") - tags_changed = current_selected_tags != st.session_state.get("previous_selected_tags", []) - - # If filters changed and we have active filters, redirect to global search - if (search_changed or tags_changed) and (current_search_term or current_selected_tags): - # Update previous values - st.session_state.previous_search_term = current_search_term - st.session_state.previous_selected_tags = current_selected_tags.copy() - # Redirect to global search page - if st.session_state.view_mode != "select_family_for_library": - st.session_state.view_mode = "select_family_for_library" - st.rerun() - # Update previous values even when filters are cleared - elif not current_search_term and not current_selected_tags: - st.session_state.previous_search_term = current_search_term - st.session_state.previous_selected_tags = current_selected_tags.copy() - st.markdown("---") if not st.session_state.editable_prompts or not any(st.session_state.editable_prompts.values()): @@ -791,22 +764,13 @@ with tab_injection: st.rerun() # --- Main Display Area --- -# Handle force selection after injection -if st.session_state.get('force_select_family_name'): - st.session_state.family_selector_edition = st.session_state.force_select_family_name - st.session_state.force_select_family_name = None - -if st.session_state.get('force_select_use_case_name'): - st.session_state.use_case_selector_edition = st.session_state.force_select_use_case_name - st.session_state.force_select_use_case_name = None - final_selected_family_edition = st.session_state.get('family_selector_edition') final_selected_use_case_edition = st.session_state.get('use_case_selector_edition') library_family_to_display = st.session_state.get('library_selected_family_for_display') # NOUVELLE SECTION POUR LA PAGE D'ACCUEIL if st.session_state.view_mode == "accueil": - st.header("Bienvenue dans votre laboratoire des prompts IA ! 💡") + st.header("Bienvenue à tous dans votre laboratoire des prompts IA ! 💡") st.caption(f"Créé par le pôle Data / IA") st.markdown(""" Vous êtes au bon endroit pour maîtriser l'art de "parler" aux Intelligences Artificielles (IA) et obtenir d'elles exactement ce dont vous avez besoin ! @@ -843,127 +807,32 @@ elif st.session_state.view_mode == "select_family_for_library": if st.button("⬅️ Retour à l'accueil", key="back_to_accueil_from_select_family"): st.session_state.view_mode = "accueil" st.rerun() - - # Check if global filters are active - search_term_lib = st.session_state.get("library_search_term", "").strip().lower() - selected_tags_lib = st.session_state.get("library_selected_tags", []) - has_active_filters = bool(search_term_lib or selected_tags_lib) - - if has_active_filters: - st.header("📚 Résultats de recherche dans tous les métiers") - if search_term_lib and selected_tags_lib: - st.markdown(f"Recherche: **{search_term_lib}** | Tags: **{', '.join(selected_tags_lib)}**") - elif search_term_lib: - st.markdown(f"Recherche: **{search_term_lib}**") - elif selected_tags_lib: - st.markdown(f"Tags: **{', '.join(selected_tags_lib)}**") + st.header("📚 Explorer les prompts par métier") + st.markdown("Cliquez sur le nom d'un métier pour afficher les prompts associés.") + st.markdown("---") + + available_families = list(st.session_state.editable_prompts.keys()) + + if not available_families: + st.info("Aucun métier de prompts n'a été créé pour le moment.") + st.markdown("Vous pouvez en créer via l'onglet **Édition** dans le menu latéral (accessible via l'icône Menu en haut à gauche).") st.markdown("---") + + else: + sorted_families = sorted(available_families) - # Global filtering across all families - global_filtered_results = {} - for family_name, use_cases in st.session_state.editable_prompts.items(): - family_filtered_use_cases = {} - for uc_name, uc_config in use_cases.items(): - # Apply same filtering logic as in library view - match_search = True - if search_term_lib: - match_search = (search_term_lib in uc_name.lower() or - search_term_lib in uc_config.get("template", "").lower() or - any(search_term_lib in var.get("name","").lower() or search_term_lib in var.get("label","").lower() - for var in uc_config.get("variables", [])) or - any(search_term_lib in tag.lower() for tag in uc_config.get("tags", []))) - match_tags = True - if selected_tags_lib: - match_tags = all(tag in uc_config.get("tags", []) for tag in selected_tags_lib) - - if match_search and match_tags: - family_filtered_use_cases[uc_name] = uc_config - - if family_filtered_use_cases: - global_filtered_results[family_name] = family_filtered_use_cases - - if not global_filtered_results: - st.info("Aucun prompt ne correspond à vos critères de recherche dans tous les métiers.") - else: - # Display results grouped by family - for family_name, filtered_use_cases in sorted(global_filtered_results.items()): - st.subheader(f"🔹 {family_name}") - sorted_use_cases = sorted(filtered_use_cases.keys()) - for uc_name in sorted_use_cases: - uc_config = filtered_use_cases[uc_name] - exp_title = f"{uc_name}" - if uc_config.get("usage_count", 0) > 0: - exp_title += f" (Utilisé {uc_config.get('usage_count')} fois)" - - with st.expander(exp_title, expanded=False): - # Display tags in the same format as standard library view - tags_display = uc_config.get("tags", []) - if tags_display: - st.markdown(f"**Tags :** {', '.join([f'`{tag}`' for tag in tags_display])}") - - # Display creation and modification dates - created_at_str = uc_config.get('created_at', get_default_dates()[0]) - updated_at_str = uc_config.get('updated_at', get_default_dates()[1]) - st.caption(f"Créé le : {datetime.fromisoformat(created_at_str).strftime('%d/%m/%Y %H:%M')} | Modifié le : {datetime.fromisoformat(updated_at_str).strftime('%d/%m/%Y %H:%M')}") - - # Three buttons in columns - same format as standard library view - col_btn_lib1, col_btn_lib2, col_btn_lib3 = st.columns(3) - with col_btn_lib1: - if st.button(f"✍️ Utiliser ce prompt", key=f"global_lib_use_{family_name.replace(' ', '_')}_{uc_name.replace(' ', '_')}", use_container_width=True): - st.session_state.view_mode = "generator" - st.session_state.generator_selected_family = family_name - st.session_state.generator_selected_use_case = uc_name - st.session_state.active_generated_prompt = "" - st.rerun() - with col_btn_lib2: - if st.button(f"📋 Dupliquer ce prompt", key=f"global_lib_duplicate_{family_name.replace(' ', '_')}_{uc_name.replace(' ', '_')}", use_container_width=True): - st.session_state.duplicating_use_case_details = { - "family": family_name, - "use_case": uc_name - } - st.rerun() - with col_btn_lib3: - if st.button(f"🗑️ Supprimer ce prompt", key=f"global_lib_delete_{family_name.replace(' ', '_')}_{uc_name.replace(' ', '_')}", use_container_width=True): - st.session_state.confirming_delete_details = { - "family": family_name, - "use_case": uc_name - } - st.rerun() - st.markdown("---") + # Vous pouvez ajuster le nombre de colonnes si vous avez beaucoup de familles + num_cols = 3 + cols = st.columns(num_cols) + for i, family_name in enumerate(sorted_families): + with cols[i % num_cols]: + if st.button(f"{family_name}", key=f"select_family_for_lib_btn_{family_name}", use_container_width=True, help=f"Voir les prompts du métier '{family_name}'"): + st.session_state.library_selected_family_for_display = family_name + st.session_state.view_mode = "library" # Redirige vers la bibliothèque avec la famille sélectionnée + st.rerun() - # Clear filters option - if st.button("🗑️ Effacer les filtres", type="secondary"): - st.session_state.library_search_term = "" - st.session_state.library_selected_tags = [] - st.rerun() - - else: - st.header("📚 Explorer les prompts par métier") - st.markdown("Cliquez sur le nom d'un métier pour afficher les prompts associés.") st.markdown("---") - available_families = list(st.session_state.editable_prompts.keys()) - - if not available_families: - st.info("Aucun métier de prompts n'a été créé pour le moment.") - st.markdown("Vous pouvez en créer via l'onglet **Édition** dans le menu latéral (accessible via l'icône Menu en haut à gauche).") - st.markdown("---") - - else: - sorted_families = sorted(available_families) - - # Vous pouvez ajuster le nombre de colonnes si vous avez beaucoup de familles - num_cols = 3 - cols = st.columns(num_cols) - for i, family_name in enumerate(sorted_families): - with cols[i % num_cols]: - if st.button(f"{family_name}", key=f"select_family_for_lib_btn_{family_name}", use_container_width=True, help=f"Voir les prompts du métier '{family_name}'"): - st.session_state.library_selected_family_for_display = family_name - st.session_state.view_mode = "library" # Redirige vers la bibliothèque avec la famille sélectionnée - st.rerun() - - st.markdown("---") - elif st.session_state.view_mode == "library": if st.button("⬅️ Retour à la sélection des métiers", key="back_to_select_family_from_library"): st.session_state.view_mode = "select_family_for_library" @@ -989,8 +858,7 @@ elif st.session_state.view_mode == "library": match_search = (search_term_lib in uc_name.lower() or search_term_lib in uc_config.get("template", "").lower() or any(search_term_lib in var.get("name","").lower() or search_term_lib in var.get("label","").lower() - for var in uc_config.get("variables", [])) or - any(search_term_lib in tag.lower() for tag in uc_config.get("tags", []))) + for var in uc_config.get("variables", []))) match_tags = True if selected_tags_lib: match_tags = all(tag in uc_config.get("tags", []) for tag in selected_tags_lib) if match_search and match_tags: filtered_use_cases[uc_name] = uc_config @@ -998,98 +866,6 @@ elif st.session_state.view_mode == "library": if not use_cases_in_family_display: st.info(f"Le métier '{library_family_to_display}' ne contient actuellement aucun prompt.") else: st.info("Aucun prompt ne correspond à vos critères de recherche/filtre dans cette métier.") else: - # Gestion de la duplication de cas d'usage - if st.session_state.duplicating_use_case_details and \ - st.session_state.duplicating_use_case_details["family"] == library_family_to_display: - - original_uc_name_for_dup = st.session_state.duplicating_use_case_details["use_case"] - original_family_name_for_dup = st.session_state.duplicating_use_case_details["family"] - - st.markdown(f"### 📋 Dupliquer '{original_uc_name_for_dup}' (depuis: {original_family_name_for_dup})") - - form_key_duplicate = f"form_duplicate_lib_{original_family_name_for_dup.replace(' ','_')}_{original_uc_name_for_dup.replace(' ','_')}" - with st.form(key=form_key_duplicate): - available_families_list = list(st.session_state.editable_prompts.keys()) - try: - default_family_idx = available_families_list.index(original_family_name_for_dup) - except ValueError: - default_family_idx = 0 - - selected_target_family_for_duplicate = st.selectbox( - "Choisir la famille de destination pour la copie :", - options=available_families_list, - index=default_family_idx, - key=f"target_family_dup_select_{form_key_duplicate}" - ) - - suggested_new_name_base = f"{original_uc_name_for_dup} (copie)" - suggested_new_name = suggested_new_name_base - temp_copy_count = 1 - while suggested_new_name in st.session_state.editable_prompts.get(selected_target_family_for_duplicate, {}): - suggested_new_name = f"{suggested_new_name_base} {temp_copy_count}" - temp_copy_count += 1 - - new_duplicated_uc_name_input = st.text_input( - "Nouveau nom pour le cas d'usage dupliqué:", - value=suggested_new_name, - key=f"new_dup_name_input_{form_key_duplicate}" - ) - - submitted_duplicate_form = st.form_submit_button("✅ Confirmer la Duplication", use_container_width=True) - - if submitted_duplicate_form: - new_uc_name_val_from_form = new_duplicated_uc_name_input.strip() - target_family_on_submit = selected_target_family_for_duplicate - - if not new_uc_name_val_from_form: - st.error("Le nom du nouveau cas d'usage ne peut pas être vide.") - elif new_uc_name_val_from_form in st.session_state.editable_prompts.get(target_family_on_submit, {}): - st.error(f"Un cas d'usage nommé '{new_uc_name_val_from_form}' existe déjà dans la famille '{target_family_on_submit}'.") - else: - current_prompt_config = st.session_state.editable_prompts[original_family_name_for_dup][original_uc_name_for_dup] - st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form] = copy.deepcopy(current_prompt_config) - now_iso_dup_create, now_iso_dup_update = get_default_dates() - st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["created_at"] = now_iso_dup_create - st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["updated_at"] = now_iso_dup_update - st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["usage_count"] = 0 - save_editable_prompts_to_local() - st.success(f"Cas d'usage '{original_uc_name_for_dup}' dupliqué en '{new_uc_name_val_from_form}' dans la famille '{target_family_on_submit}'.") - - st.session_state.duplicating_use_case_details = None - if target_family_on_submit != library_family_to_display: - st.session_state.library_selected_family_for_display = target_family_on_submit - st.rerun() - - cancel_key_duplicate = f"cancel_dup_process_lib_{original_family_name_for_dup.replace(' ','_')}_{original_uc_name_for_dup.replace(' ','_')}" - if st.button("❌ Annuler la Duplication", key=cancel_key_duplicate, use_container_width=True): - st.session_state.duplicating_use_case_details = None - st.rerun() - - st.markdown("---") - - # Gestion de la suppression de cas d'usage - if st.session_state.confirming_delete_details and \ - st.session_state.confirming_delete_details["family"] == library_family_to_display: - - details = st.session_state.confirming_delete_details - st.warning(f"⚠️ Supprimer '{details['use_case']}' de '{details['family']}' ? Action irréversible.") - - c1_del_uc, c2_del_uc, _ = st.columns([1,1,3]) - if c1_del_uc.button(f"Oui, supprimer '{details['use_case']}'", key=f"del_yes_lib_{details['family']}_{details['use_case']}", type="primary"): - deleted_uc_name_for_msg = details['use_case'] - deleted_uc_fam_for_msg = details['family'] - del st.session_state.editable_prompts[details["family"]][details["use_case"]] - save_editable_prompts_to_local() - st.success(f"'{deleted_uc_name_for_msg}' supprimé de '{deleted_uc_fam_for_msg}'.") - st.session_state.confirming_delete_details = None - st.rerun() - - if c2_del_uc.button("Non, annuler", key=f"del_no_lib_{details['family']}_{details['use_case']}"): - st.session_state.confirming_delete_details = None - st.rerun() - - st.markdown("---") - sorted_use_cases_display = sorted(list(filtered_use_cases.keys())) for use_case_name_display in sorted_use_cases_display: prompt_config_display = filtered_use_cases[use_case_name_display] @@ -1102,26 +878,15 @@ elif st.session_state.view_mode == "library": if tags_display: st.markdown(f"**Tags :** {', '.join([f'`{tag}`' for tag in tags_display])}") created_at_str = prompt_config_display.get('created_at', get_default_dates()[0]) updated_at_str = prompt_config_display.get('updated_at', get_default_dates()[1]) - st.caption(f"Créé le : {datetime.fromisoformat(created_at_str).strftime('%d/%m/%Y %H:%M')} | Modifié le : {datetime.fromisoformat(updated_at_str).strftime('%d/%m/%Y %H:%M')}") + st.caption(f"Créé le: {datetime.fromisoformat(created_at_str).strftime('%d/%m/%Y %H:%M')} | Modifié le: {datetime.fromisoformat(updated_at_str).strftime('%d/%m/%Y %H:%M')}") - col_btn_lib1, col_btn_lib2, col_btn_lib3 = st.columns(3) + col_btn_lib1, col_btn_lib2 = st.columns(2) with col_btn_lib1: if st.button(f"✍️ Utiliser ce prompt", key=f"main_lib_use_{library_family_to_display.replace(' ', '_')}_{use_case_name_display.replace(' ', '_')}", use_container_width=True): - st.session_state.view_mode = "generator"; st.session_state.generator_selected_family = library_family_to_display; st.session_state.generator_selected_use_case = use_case_name_display; st.session_state.active_generated_prompt = ""; st.rerun() + st.session_state.view_mode = "edit"; st.session_state.force_select_family_name = library_family_to_display; st.session_state.force_select_use_case_name = use_case_name_display; st.session_state.go_to_config_section = False; st.session_state.active_generated_prompt = ""; st.session_state.variable_type_to_create = None; st.session_state.editing_variable_info = None; st.session_state.confirming_delete_details = None; st.rerun() with col_btn_lib2: - if st.button(f"📋 Dupliquer ce prompt", key=f"main_lib_duplicate_{library_family_to_display.replace(' ', '_')}_{use_case_name_display.replace(' ', '_')}", use_container_width=True): - st.session_state.duplicating_use_case_details = { - "family": library_family_to_display, - "use_case": use_case_name_display - } - st.rerun() - with col_btn_lib3: - if st.button(f"🗑️ Supprimer ce prompt", key=f"main_lib_delete_{library_family_to_display.replace(' ', '_')}_{use_case_name_display.replace(' ', '_')}", use_container_width=True): - st.session_state.confirming_delete_details = { - "family": library_family_to_display, - "use_case": use_case_name_display - } - st.rerun() + if st.button(f"⚙️ Éditer ce prompt", key=f"main_lib_edit_{library_family_to_display.replace(' ', '_')}_{use_case_name_display.replace(' ', '_')}", use_container_width=True): + st.session_state.view_mode = "edit"; st.session_state.force_select_family_name = library_family_to_display; st.session_state.force_select_use_case_name = use_case_name_display; st.session_state.go_to_config_section = True; st.session_state.active_generated_prompt = ""; st.session_state.variable_type_to_create = None; st.session_state.editing_variable_info = None; st.session_state.confirming_delete_details = None; st.rerun() else: st.info("Aucun métier n'est actuellement sélectionnée dans la bibliothèque ou le métier sélectionné n'existe plus.") available_families_check = list(st.session_state.editable_prompts.keys()) @@ -1140,14 +905,20 @@ elif st.session_state.view_mode == "edit": current_prompt_config = st.session_state.editable_prompts[final_selected_family_edition][final_selected_use_case_edition] st.header(f"Cas d'usage: {final_selected_use_case_edition}") created_at_str_edit = current_prompt_config.get('created_at', get_default_dates()[0]); updated_at_str_edit = current_prompt_config.get('updated_at', get_default_dates()[1]) - st.caption(f"Métier : {final_selected_family_edition} | Utilisé {current_prompt_config.get('usage_count', 0)} fois. Créé le : {datetime.fromisoformat(created_at_str_edit).strftime('%d/%m/%Y')}, Modifié le : {datetime.fromisoformat(updated_at_str_edit).strftime('%d/%m/%Y')}") - # Afficher la description si elle existe - description = current_prompt_config.get("description", "").strip() - if description: - st.markdown(f"*{description}*") + st.caption(f"Métier : {final_selected_family_edition} | Utilisé {current_prompt_config.get('usage_count', 0)} fois. Créé: {datetime.fromisoformat(created_at_str_edit).strftime('%d/%m/%Y')}, Modifié: {datetime.fromisoformat(updated_at_str_edit).strftime('%d/%m/%Y')}") + st.markdown(""" +
+

Comment ça marche ?

+
    +
  1. Remplissez les champs ci-dessous : Chaque champ correspond à une information variable (comme un nom, une date, un sujet spécifique) que vous souhaitez insérer dans votre instruction finale pour l'IA.
  2. +
  3. Cliquez sur "🚀 Générer Prompt" : L'application prendra le modèle de base de ce prompt et y insérera les informations que vous avez fournies.
  4. +
  5. Utilisez votre prompt : Le prompt complet et personnalisé apparaîtra plus bas. Vous pourrez le copier pour l'utiliser avec l'outil d'IA de votre choix.
  6. +
+

💡 Bon à savoir : Le modèle de base de ce prompt (le "template") ainsi que la liste des variables demandées sont entièrement personnalisables ! Vous pouvez les modifier dans la section "⚙️ Paramétrage du Prompt" qui se trouve plus bas sur cette même page (dans le menu déroulant).

+
+ """, unsafe_allow_html=True) gen_form_values = {} with st.form(key=f"gen_form_{final_selected_family_edition}_{final_selected_use_case_edition}"): - st.markdown("**Remplissez le formulaire ci-dessous pour ajouter du contexte à votre prompt :**") if not current_prompt_config.get("variables"): st.info("Ce cas d'usage n'a pas de variables configurées pour la génération.") variables_for_form = current_prompt_config.get("variables", []) if not isinstance(variables_for_form, list): variables_for_form = [] @@ -1232,7 +1003,7 @@ elif st.session_state.view_mode == "edit": st.balloons() current_prompt_config["usage_count"] = current_prompt_config.get("usage_count", 0) + 1 current_prompt_config["updated_at"] = datetime.now().isoformat() - save_editable_prompts_to_local() + save_editable_prompts_to_gist() except Exception as e: # Garder un catch-all pour les erreurs imprévues st.error(f"Erreur inattendue lors de la génération du prompt : {e}") # pragma: no cover @@ -1259,25 +1030,274 @@ elif st.session_state.view_mode == "edit": prompt_text_escaped_for_js = json.dumps(st.session_state.active_generated_prompt) - should_expand_config = SHOW_CONFIG_SECTION and st.session_state.get('go_to_config_section', False) - if SHOW_CONFIG_SECTION: - with st.expander(f"⚙️ Paramétrage du Prompt: {final_selected_use_case_edition}", expanded=should_expand_config): - st.info("Section de paramétrage temporairement désactivée.") - - if st.session_state.get('go_to_config_section'): - st.session_state.go_to_config_section = False if st.session_state.confirming_delete_details and st.session_state.confirming_delete_details["family"] == final_selected_family_edition and st.session_state.confirming_delete_details["use_case"] == final_selected_use_case_edition: details = st.session_state.confirming_delete_details; st.warning(f"Supprimer '{details['use_case']}' de '{details['family']}' ? Action irréversible.") c1_del_uc, c2_del_uc, _ = st.columns([1,1,3]) if c1_del_uc.button(f"Oui, supprimer '{details['use_case']}'", key=f"del_yes_{details['family']}_{details['use_case']}", type="primary"): - deleted_uc_name_for_msg = details['use_case']; deleted_uc_fam_for_msg = details['family']; del st.session_state.editable_prompts[details["family"]][details["use_case"]]; save_editable_prompts_to_local(); st.success(f"'{deleted_uc_name_for_msg}' supprimé de '{deleted_uc_fam_for_msg}'.") + deleted_uc_name_for_msg = details['use_case']; deleted_uc_fam_for_msg = details['family']; del st.session_state.editable_prompts[details["family"]][details["use_case"]]; save_editable_prompts_to_gist(); st.success(f"'{deleted_uc_name_for_msg}' supprimé de '{deleted_uc_fam_for_msg}'.") st.session_state.confirming_delete_details = None; st.session_state.force_select_family_name = deleted_uc_fam_for_msg; st.session_state.force_select_use_case_name = None if st.session_state.editing_variable_info and st.session_state.editing_variable_info.get("family") == deleted_uc_fam_for_msg and st.session_state.editing_variable_info.get("use_case") == deleted_uc_name_for_msg: st.session_state.editing_variable_info = None # pragma: no cover st.session_state.active_generated_prompt = ""; st.session_state.variable_type_to_create = None; st.session_state.view_mode = "edit"; st.rerun() if c2_del_uc.button("Non, annuler", key=f"del_no_{details['family']}_{details['use_case']}"): st.session_state.confirming_delete_details = None; st.rerun() st.markdown("---") - + should_expand_config = st.session_state.get('go_to_config_section', False) + with st.expander(f"⚙️ Paramétrage du Prompt: {final_selected_use_case_edition}", expanded=should_expand_config): + st.subheader("Template du Prompt") + safe_family_key_part = str(final_selected_family_edition).replace(' ', '_').replace('.', '_').replace('{', '_').replace('}', '_').replace('(', '_').replace(')', '_'); safe_uc_key_part = str(final_selected_use_case_edition).replace(' ', '_').replace('.', '_').replace('{', '_').replace('}', '_').replace('(', '_').replace(')', '_') + template_text_area_key = f"template_text_area_{safe_family_key_part}_{safe_uc_key_part}"; new_tpl = st.text_area("Template:", value=current_prompt_config.get('template', ''), height=200, key=template_text_area_key) + st.markdown("""""", unsafe_allow_html=True) + st.markdown("##### Variables disponibles à insérer :"); variables_config = current_prompt_config.get('variables', []) + if not variables_config: st.caption("Aucune variable définie pour ce prompt. Ajoutez-en ci-dessous.") + else: + col1, col2 = st.columns(2) + for i, var_info in enumerate(variables_config): + if 'name' in var_info: + variable_string_to_display = f"{{{var_info['name']}}}"; target_column = col1 if i % 2 == 0 else col2 + with target_column: st.code(variable_string_to_display, language=None) + st.caption("Survolez une variable ci-dessus et cliquez sur l'icône qui apparaît pour la copier.") + save_template_button_key = f"save_template_button_{safe_family_key_part}_{safe_uc_key_part}" + if st.button("Sauvegarder Template", key=save_template_button_key): current_prompt_config['template'] = new_tpl; current_prompt_config["updated_at"] = datetime.now().isoformat(); save_editable_prompts_to_gist(); st.success("Template sauvegardé!"); st.rerun() + st.markdown("---"); st.subheader("Variables du Prompt"); current_variables_list = current_prompt_config.get('variables', []) + if not current_variables_list: st.info("Aucune variable définie.") + else: pass + for idx, var_data in enumerate(list(current_variables_list)): + var_id_for_key = var_data.get('name', f"varidx{idx}").replace(" ", "_"); action_key_prefix = f"var_action_{final_selected_family_edition.replace(' ','_')}_{final_selected_use_case_edition.replace(' ','_')}_{var_id_for_key}" + col_info, col_up, col_down, col_edit, col_delete = st.columns([3, 0.5, 0.5, 0.8, 0.8]) + with col_info: st.markdown(f"**{idx + 1}. {var_data.get('name', 'N/A')}** ({var_data.get('label', 'N/A')})\n*Type: `{var_data.get('type', 'N/A')}`*") + with col_up: + disable_up_button = (idx == 0) + if st.button("↑", key=f"{action_key_prefix}_up", help="Monter cette variable", disabled=disable_up_button, use_container_width=True): current_variables_list[idx], current_variables_list[idx-1] = current_variables_list[idx-1], current_variables_list[idx]; current_prompt_config["variables"] = current_variables_list; current_prompt_config["updated_at"] = datetime.now().isoformat(); save_editable_prompts_to_gist(); st.session_state.editing_variable_info = None; st.session_state.variable_type_to_create = None; st.rerun() + with col_down: + disable_down_button = (idx == len(current_variables_list) - 1) + if st.button("↓", key=f"{action_key_prefix}_down", help="Descendre cette variable", disabled=disable_down_button, use_container_width=True): current_variables_list[idx], current_variables_list[idx+1] = current_variables_list[idx+1], current_variables_list[idx]; current_prompt_config["variables"] = current_variables_list; current_prompt_config["updated_at"] = datetime.now().isoformat(); save_editable_prompts_to_gist(); st.session_state.editing_variable_info = None; st.session_state.variable_type_to_create = None; st.rerun() + with col_edit: + if st.button("Modifier", key=f"{action_key_prefix}_edit", use_container_width=True): st.session_state.editing_variable_info = { "family": final_selected_family_edition, "use_case": final_selected_use_case_edition, "index": idx, "data": copy.deepcopy(var_data) }; st.session_state.variable_type_to_create = var_data.get('type'); st.rerun() + with col_delete: + if st.button("Suppr.", key=f"{action_key_prefix}_delete", type="secondary", use_container_width=True): variable_name_to_delete = current_variables_list.pop(idx).get('name', 'Variable inconnue'); current_prompt_config["variables"] = current_variables_list; current_prompt_config["updated_at"] = datetime.now().isoformat(); save_editable_prompts_to_gist(); st.success(f"Variable '{variable_name_to_delete}' supprimée."); st.session_state.editing_variable_info = None; st.session_state.variable_type_to_create = None; st.rerun() + st.markdown("---"); st.subheader("Ajouter une Variable"); is_editing_var = False; variable_data_for_form = {"name": "", "label": "", "type": "", "options": "", "default": ""} + if st.session_state.editing_variable_info and st.session_state.editing_variable_info.get("family") == final_selected_family_edition and st.session_state.editing_variable_info.get("use_case") == final_selected_use_case_edition: + edit_var_idx = st.session_state.editing_variable_info["index"] + if edit_var_idx < len(current_prompt_config.get('variables',[])): + is_editing_var = True; current_editing_data_snapshot = current_prompt_config['variables'][edit_var_idx]; variable_data_for_form.update(copy.deepcopy(current_editing_data_snapshot)) + if isinstance(variable_data_for_form.get("options"), list): variable_data_for_form["options"] = ", ".join(map(str, variable_data_for_form["options"])) + raw_def_edit_form = variable_data_for_form.get("default") + if isinstance(raw_def_edit_form, date): variable_data_for_form["default"] = raw_def_edit_form.strftime("%Y-%m-%d") + elif raw_def_edit_form is not None: variable_data_for_form["default"] = str(raw_def_edit_form) + else: variable_data_for_form["default"] = "" + else: st.session_state.editing_variable_info = None; st.session_state.variable_type_to_create = None; st.warning("La variable que vous tentiez de modifier n'existe plus. Annulation de l'édition."); st.rerun() # pragma: no cover + if not is_editing_var and st.session_state.variable_type_to_create is None: + st.markdown("##### 1. Choisissez le type de variable à créer :"); variable_types_map = { "Zone de texte (courte)": "text_input", "Liste choix": "selectbox", "Date": "date_input", "Nombre": "number_input", "Zone de texte (longue)": "text_area" }; num_type_buttons = len(variable_types_map); cols_type_buttons = st.columns(min(num_type_buttons, 5)); button_idx = 0 + for btn_label, type_val in variable_types_map.items(): + if cols_type_buttons[button_idx % len(cols_type_buttons)].button(btn_label, key=f"btn_type_{type_val}_{final_selected_use_case_edition.replace(' ','_')}", use_container_width=True): st.session_state.variable_type_to_create = type_val; st.rerun() + button_idx += 1 + st.markdown("---") + if st.session_state.variable_type_to_create: + current_type_for_form = st.session_state.variable_type_to_create + variable_types_map_display = { + "text_input": "Zone de texte (courte)", "selectbox": "Liste choix", + "date_input": "Date", "number_input": "Nombre", "text_area": "Zone de texte (longue)" + } + readable_type = variable_types_map_display.get(current_type_for_form, "Type Inconnu") + form_title = f"Modifier Variable : {variable_data_for_form.get('name','N/A')} ({readable_type})" if is_editing_var else f"Nouvelle Variable : {readable_type}" + st.markdown(f"##### 2. Configurez la variable") + + form_key_suffix = f"_edit_{st.session_state.editing_variable_info['index']}" if is_editing_var and st.session_state.editing_variable_info else "_create" + form_var_specific_key = f"form_var_{current_type_for_form}_{final_selected_use_case_edition.replace(' ','_')}{form_key_suffix}" + + # --- DÉBUT DU FORMULAIRE --- + with st.form(key=form_var_specific_key, clear_on_submit=(not is_editing_var)): + st.subheader(form_title) + var_name_input_form = st.text_input( + "Nom technique (ex : nom_client. Ne pas utiliser de caractères spéciaux -espaces, crochets {},virgules, etc.-)", + value=variable_data_for_form.get("name", ""), + key=f"{form_var_specific_key}_name", + disabled=is_editing_var + ) + var_label_input_form = st.text_input( + "Label pour l'utilisateur (description affichée)", + value=variable_data_for_form.get("label", ""), + key=f"{form_var_specific_key}_label" + ) + var_options_str_input_form = "" + if current_type_for_form == "selectbox": + var_options_str_input_form = st.text_input( + "Options (séparées par une virgule)", + value=variable_data_for_form.get("options", ""), + key=f"{form_var_specific_key}_options" + ) + date_hint = " (Format AAAA-MM-JJ)" if current_type_for_form == "date_input" else "" + var_default_val_str_input_form = st.text_input( + f"Valeur par défaut{date_hint}", + value=str(variable_data_for_form.get("default", "")), + key=f"{form_var_specific_key}_default" + ) + + min_val_input_form, max_val_input_form, step_val_input_form, height_val_input_form = None, None, None, None + if current_type_for_form == "number_input": + num_cols_var_form = st.columns(3) + min_val_edit_default = variable_data_for_form.get("min_value") + max_val_edit_default = variable_data_for_form.get("max_value") + step_val_edit_default = variable_data_for_form.get("step", 1.0) + min_val_input_form = num_cols_var_form[0].number_input("Valeur minimale (optionnel)", value=float(min_val_edit_default) if min_val_edit_default is not None else None, format="%g", key=f"{form_var_specific_key}_min") + max_val_input_form = num_cols_var_form[1].number_input("Valeur maximale (optionnel)", value=float(max_val_edit_default) if max_val_edit_default is not None else None, format="%g", key=f"{form_var_specific_key}_max") + step_val_input_form = num_cols_var_form[2].number_input("Pas (incrément)", value=float(step_val_edit_default), format="%g", min_value=1e-9, key=f"{form_var_specific_key}_step") + if current_type_for_form == "text_area": + height_val_input_form = st.number_input("Hauteur de la zone de texte (pixels)", value=int(variable_data_for_form.get("height", 100)), min_value=68, step=25, key=f"{form_var_specific_key}_height") # min_value ajusté à 68 + + submit_button_label_form = "Sauvegarder Modifications" if is_editing_var else "Ajouter Variable" + submitted_specific_var_form = st.form_submit_button(submit_button_label_form) # BOUTON DE SOUMISSION DU FORMULAIRE + + if submitted_specific_var_form: + var_name_val_submit = var_name_input_form.strip() + if not var_name_val_submit or not var_label_input_form.strip(): st.error("Le nom technique et le label de la variable sont requis.") + elif not var_name_val_submit.isidentifier(): st.error("Nom technique invalide. Utilisez lettres, chiffres, underscores. Ne pas commencer par un chiffre. Ne pas utiliser de mot-clé Python.") + elif current_type_for_form == "selectbox" and not [opt.strip() for opt in var_options_str_input_form.split(',') if opt.strip()]: st.error("Pour une variable de type 'Liste choix', au moins une option est requise.") + else: + new_var_data_to_submit = { "name": var_name_val_submit, "label": var_label_input_form.strip(), "type": current_type_for_form }; parsed_def_val_submit = parse_default_value(var_default_val_str_input_form.strip(), current_type_for_form) + if current_type_for_form == "selectbox": + options_list_submit = [opt.strip() for opt in var_options_str_input_form.split(',') if opt.strip()]; new_var_data_to_submit["options"] = options_list_submit + if options_list_submit: + if parsed_def_val_submit not in options_list_submit: st.warning(f"La valeur par défaut '{parsed_def_val_submit}' n'est pas dans la liste d'options. La première option '{options_list_submit[0]}' sera utilisée comme défaut."); new_var_data_to_submit["default"] = options_list_submit[0] + else: new_var_data_to_submit["default"] = parsed_def_val_submit + else: new_var_data_to_submit["default"] = "" # pragma: no cover + else: new_var_data_to_submit["default"] = parsed_def_val_submit + if current_type_for_form == "number_input": + if min_val_input_form is not None: new_var_data_to_submit["min_value"] = float(min_val_input_form) + if max_val_input_form is not None: new_var_data_to_submit["max_value"] = float(max_val_input_form) + if step_val_input_form is not None: new_var_data_to_submit["step"] = float(step_val_input_form) + else: new_var_data_to_submit["step"] = 1.0 + if current_type_for_form == "text_area" and height_val_input_form is not None: + new_var_data_to_submit["height"] = int(height_val_input_form) # Déjà un int grâce au widget number_input + + can_proceed_with_save = True; target_vars_list = current_prompt_config.get('variables', []) + if is_editing_var: + idx_to_edit_submit_form = st.session_state.editing_variable_info["index"]; target_vars_list[idx_to_edit_submit_form] = new_var_data_to_submit; st.success(f"Variable '{var_name_val_submit}' mise à jour avec succès."); st.session_state.editing_variable_info = None; st.session_state.variable_type_to_create = None + else: + existing_var_names_in_uc = [v['name'] for v in target_vars_list] + if var_name_val_submit in existing_var_names_in_uc: st.error(f"Une variable avec le nom technique '{var_name_val_submit}' existe déjà pour ce cas d'usage."); can_proceed_with_save = False # pragma: no cover + else: target_vars_list.append(new_var_data_to_submit); st.success(f"Variable '{var_name_val_submit}' ajoutée avec succès.") + if can_proceed_with_save: + current_prompt_config["variables"] = target_vars_list; current_prompt_config["updated_at"] = datetime.now().isoformat(); save_editable_prompts_to_gist() + if not is_editing_var: st.session_state.variable_type_to_create = None + st.rerun() + # --- FIN DU BLOC st.form(...) --- + + # --- DÉPLACEMENT DU BOUTON ANNULER ICI (EN DEHORS ET APRÈS LE FORMULAIRE) --- + # Les variables is_editing_var et form_var_specific_key sont toujours accessibles ici. + cancel_button_label_form = "Annuler Modification" if is_editing_var else "Changer de Type / Annuler Création" + # Clé unique pour ce bouton, distincte de celles du formulaire si besoin + cancel_btn_key = f"cancel_var_action_btn_{form_var_specific_key}_outside" + + if st.button(cancel_button_label_form, key=cancel_btn_key, help="Réinitialise le formulaire de variable."): + st.session_state.variable_type_to_create = None + if is_editing_var: + st.session_state.editing_variable_info = None + st.rerun() + st.markdown("---"); st.subheader("🏷️ Tags"); current_tags_str = ", ".join(current_prompt_config.get("tags", [])) + new_tags_str_input = st.text_input("Tags (séparés par des virgules):", value=current_tags_str, key=f"tags_input_{final_selected_family_edition}_{final_selected_use_case_edition}") + if st.button("Sauvegarder Tags", key=f"save_tags_btn_{final_selected_family_edition}_{final_selected_use_case_edition}"): current_prompt_config["tags"] = sorted(list(set(t.strip() for t in new_tags_str_input.split(',') if t.strip()))); current_prompt_config["updated_at"] = datetime.now().isoformat(); save_editable_prompts_to_gist(); st.success("Tags sauvegardés!"); st.rerun() + # --- FIN DU BLOC if st.session_state.variable_type_to_create: --- + + st.markdown("---") + st.subheader("Actions sur le Cas d'Usage") + + if st.session_state.duplicating_use_case_details and \ + st.session_state.duplicating_use_case_details["family"] == final_selected_family_edition and \ + st.session_state.duplicating_use_case_details["use_case"] == final_selected_use_case_edition: + + original_uc_name_for_dup_form = st.session_state.duplicating_use_case_details["use_case"] + original_family_name_for_dup = st.session_state.duplicating_use_case_details["family"] # Famille d'origine + st.markdown(f"#### Dupliquer '{original_uc_name_for_dup_form}' (depuis: {original_family_name_for_dup})") + + form_key_duplicate = f"form_duplicate_name_{original_family_name_for_dup.replace(' ','_')}_{original_uc_name_for_dup_form.replace(' ','_')}" + with st.form(key=form_key_duplicate): + available_families_list = list(st.session_state.editable_prompts.keys()) + try: + default_family_idx = available_families_list.index(original_family_name_for_dup) + except ValueError: # Au cas où la famille originale aurait été supprimée entre-temps (peu probable) + default_family_idx = 0 + + selected_target_family_for_duplicate = st.selectbox( + "Choisir la famille de destination pour la copie :", + options=available_families_list, + index=default_family_idx, + key=f"target_family_dup_select_{form_key_duplicate}" + ) + + suggested_new_name_base = f"{original_uc_name_for_dup_form} (copie)" + suggested_new_name = suggested_new_name_base # Ligne 1285 originale + temp_copy_count = 1 + # La suggestion de nom initiale se base sur la famille de destination par défaut (l'originale). + # L'utilisateur devra peut-être ajuster manuellement si une collision de nom se produit après avoir changé la famille de destination. + # La validation finale se fera avec la famille de destination réellement choisie. + while suggested_new_name in st.session_state.editable_prompts.get(selected_target_family_for_duplicate, {}): # Vérifie la famille qui est sélectionnée par défaut + suggested_new_name = f"{suggested_new_name_base} {temp_copy_count}" + temp_copy_count += 1 + + new_duplicated_uc_name_input = st.text_input( + "Nouveau nom pour le cas d'usage dupliqué:", + value=suggested_new_name, # La valeur suggérée est basée sur la famille par défaut du selectbox + key=f"new_dup_name_input_{form_key_duplicate}" + ) + + submitted_duplicate_form = st.form_submit_button("✅ Confirmer la Duplication", use_container_width=True) + + if submitted_duplicate_form: + new_uc_name_val_from_form = new_duplicated_uc_name_input.strip() + target_family_on_submit = selected_target_family_for_duplicate # Récupère la famille de destination choisie + + if not new_uc_name_val_from_form: + st.error("Le nom du nouveau cas d'usage ne peut pas être vide.") + elif new_uc_name_val_from_form in st.session_state.editable_prompts.get(target_family_on_submit, {}): # Validation avec la famille de destination + st.error(f"Un cas d'usage nommé '{new_uc_name_val_from_form}' existe déjà dans la famille '{target_family_on_submit}'.") + else: + # current_prompt_config est la config du cas d'usage original + st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form] = copy.deepcopy(current_prompt_config) + now_iso_dup_create, now_iso_dup_update = get_default_dates() + st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["created_at"] = now_iso_dup_create + st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["updated_at"] = now_iso_dup_update + st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["usage_count"] = 0 + save_editable_prompts_to_gist() + st.success(f"Cas d'usage '{original_uc_name_for_dup_form}' dupliqué en '{new_uc_name_val_from_form}' dans la famille '{target_family_on_submit}'.") + + st.session_state.duplicating_use_case_details = None + st.session_state.force_select_family_name = target_family_on_submit # Navigation vers la nouvelle famille + st.session_state.force_select_use_case_name = new_uc_name_val_from_form # Navigation vers le nouveau cas d'usage + st.session_state.active_generated_prompt = "" + st.session_state.variable_type_to_create = None + st.session_state.editing_variable_info = None + st.session_state.go_to_config_section = True + st.rerun() + + cancel_key_duplicate = f"cancel_dup_process_{original_family_name_for_dup.replace(' ','_')}_{original_uc_name_for_dup_form.replace(' ','_')}" + if st.button("❌ Annuler la Duplication", key=cancel_key_duplicate, use_container_width=True): + st.session_state.duplicating_use_case_details = None + # st.session_state.go_to_config_section = True # On s'assure que l'expandeur reste ouvert même en annulant + st.rerun() + else: + action_cols_manage = st.columns(2) + with action_cols_manage[0]: + dup_key_init = f"initiate_dup_uc_btn_{final_selected_family_edition.replace(' ','_')}_{final_selected_use_case_edition.replace(' ','_')}" + if st.button("🔄 Dupliquer ce Cas d'Usage", key=dup_key_init, use_container_width=True): + st.session_state.duplicating_use_case_details = { + "family": final_selected_family_edition, + "use_case": final_selected_use_case_edition + } + st.session_state.go_to_config_section = True + st.rerun() + + with action_cols_manage[1]: + del_uc_key_exp_main = f"del_uc_btn_exp_main_{final_selected_family_edition.replace(' ','_')}_{final_selected_use_case_edition.replace(' ','_')}" + is_confirming_this_uc_delete_main = bool(st.session_state.confirming_delete_details and \ + st.session_state.confirming_delete_details.get("family") == final_selected_family_edition and \ + st.session_state.confirming_delete_details.get("use_case") == final_selected_use_case_edition) + if st.button("🗑️ Supprimer Cas d'Usage", key=del_uc_key_exp_main, type="secondary", disabled=is_confirming_this_uc_delete_main, use_container_width=True): + st.session_state.confirming_delete_details = {"family": final_selected_family_edition, "use_case": final_selected_use_case_edition} + st.rerun() + + if st.session_state.get('go_to_config_section'): + st.session_state.go_to_config_section = False else: if not final_selected_family_edition: st.info("Veuillez sélectionner un métier dans la barre latérale (onglet Édition) pour commencer.") @@ -1287,199 +1307,6 @@ elif st.session_state.view_mode == "edit": st.warning(f"Le cas d'usage '{final_selected_use_case_edition}' dans le métier '{final_selected_family_edition}' semble introuvable. Il a peut-être été supprimé. Veuillez vérifier vos sélections.") st.session_state.use_case_selector_edition = None # pragma: no cover -elif st.session_state.view_mode == "generator": - generator_family = st.session_state.get('generator_selected_family') - generator_use_case = st.session_state.get('generator_selected_use_case') - - if st.button(f"⬅️ Retour à la bibliothèque ({generator_family or 'Métier'})", key="back_to_library_from_generator"): - if generator_family: - st.session_state.library_selected_family_for_display = generator_family - st.session_state.view_mode = "library" - st.rerun() - - if not generator_family or not generator_use_case: - st.info("Sélection de prompt invalide. Retournez à la bibliothèque pour choisir un prompt.") - elif generator_family not in st.session_state.editable_prompts or generator_use_case not in st.session_state.editable_prompts[generator_family]: - st.warning("Le prompt sélectionné n'existe plus. Retournez à la bibliothèque pour en choisir un autre.") - else: - # Bandeau image pour "Générateur de Prompt" - st.image("BandeauGener.png", use_container_width=True) - - current_prompt_config = st.session_state.editable_prompts[generator_family][generator_use_case] - st.header(f"{generator_use_case}") - created_at_str_gen = current_prompt_config.get('created_at', get_default_dates()[0]) - updated_at_str_gen = current_prompt_config.get('updated_at', get_default_dates()[1]) - st.caption(f"Métier : {generator_family} | Utilisé {current_prompt_config.get('usage_count', 0)} fois. Créé le : {datetime.fromisoformat(created_at_str_gen).strftime('%d/%m/%Y')}, Modifié le : {datetime.fromisoformat(updated_at_str_gen).strftime('%d/%m/%Y')}") - # Afficher la description si elle existe - description = current_prompt_config.get("description", "").strip() - if description: - st.markdown(f"*{description}*") - gen_form_values = {} - with st.form(key=f"gen_form_{generator_family}_{generator_use_case}"): - st.markdown("**Remplissez le formulaire ci-dessous pour ajouter du contexte à votre prompt :**") - if not current_prompt_config.get("variables"): - st.info("Ce cas d'usage n'a pas de variables configurées pour la génération.") - - variables_for_form = current_prompt_config.get("variables", []) - if not isinstance(variables_for_form, list): - variables_for_form = [] - - cols_per_row = 2 if len(variables_for_form) > 1 else 1 - var_chunks = [variables_for_form[i:i + cols_per_row] for i in range(0, len(variables_for_form), cols_per_row)] - - for chunk in var_chunks: - cols = st.columns(len(chunk)) - for i, var_info in enumerate(chunk): - with cols[i]: - widget_key = f"gen_input_{generator_family}_{generator_use_case}_{var_info['name']}" - field_default = var_info.get("default") - var_type = var_info.get("type") - - if var_type == "text_input": - gen_form_values[var_info["name"]] = st.text_input( - var_info["label"], - value=str(field_default or ""), - key=widget_key - ) - elif var_type == "selectbox": - opts = var_info.get("options", []) - idx = 0 - if opts: - try: - idx = opts.index(field_default) if field_default in opts else 0 - except ValueError: - idx = 0 - gen_form_values[var_info["name"]] = st.selectbox( - var_info["label"], - options=opts, - index=idx, - key=widget_key - ) - elif var_type == "date_input": - val_date = field_default if isinstance(field_default, date) else datetime.now().date() - gen_form_values[var_info["name"]] = st.date_input( - var_info["label"], - value=val_date, - key=widget_key - ) - elif var_type == "number_input": - current_value_default_gen = var_info.get("default") - min_val_config_gen = var_info.get("min_value") - max_val_config_gen = var_info.get("max_value") - step_config_gen = var_info.get("step") - val_num_gen = float(current_value_default_gen) if isinstance(current_value_default_gen, (int, float)) else 0.0 - min_val_gen = float(min_val_config_gen) if min_val_config_gen is not None else None - max_val_gen = float(max_val_config_gen) if max_val_config_gen is not None else None - step_val_gen = float(step_config_gen) if step_config_gen is not None else 1.0 - if min_val_gen is not None and val_num_gen < min_val_gen: - val_num_gen = min_val_gen - if max_val_gen is not None and val_num_gen > max_val_gen: - val_num_gen = max_val_gen - gen_form_values[var_info["name"]] = st.number_input( - var_info["label"], - value=val_num_gen, - min_value=min_val_gen, - max_value=max_val_gen, - step=step_val_gen, - key=widget_key, - format="%.2f" - ) - elif var_type == "text_area": - height_val = var_info.get("height") - final_height = None - if height_val is not None: - try: - h = int(height_val) - if h >= 68: - final_height = h - else: - final_height = 68 - except (ValueError, TypeError): - final_height = None - else: - final_height = None - gen_form_values[var_info["name"]] = st.text_area( - var_info["label"], - value=str(field_default or ""), - height=final_height, - key=widget_key - ) - - if st.form_submit_button("🚀 Générer Prompt"): - processed_values_for_template = {} - for k, v_val in gen_form_values.items(): - if v_val is None: - continue - - if isinstance(v_val, date): - processed_values_for_template[k] = v_val.strftime("%d/%m/%Y") - elif isinstance(v_val, float): - if v_val.is_integer(): - processed_values_for_template[k] = str(int(v_val)) - else: - processed_values_for_template[k] = f"{v_val:.2f}" - else: - processed_values_for_template[k] = str(v_val) - - final_vals_for_prompt = processed_values_for_template - - try: - prompt_template_content = current_prompt_config.get("template", "") - processed_template = prompt_template_content - - sorted_vars_for_formatting = sorted(final_vals_for_prompt.items(), key=lambda item: len(item[0]), reverse=True) - - for var_name, var_value in sorted_vars_for_formatting: - placeholder_streamlit = f"{{{var_name}}}" - processed_template = processed_template.replace(placeholder_streamlit, str(var_value)) - - formatted_template_content = processed_template.replace("{{", "{").replace("}}", "}") - - use_case_title = generator_use_case - generated_prompt = f"Sujet : {use_case_title}\n{formatted_template_content}" - st.session_state.active_generated_prompt = generated_prompt - st.success("Prompt généré avec succès!") - st.balloons() - current_prompt_config["usage_count"] = current_prompt_config.get("usage_count", 0) + 1 - current_prompt_config["updated_at"] = datetime.now().isoformat() - save_editable_prompts_to_local() - - except Exception as e: - st.error(f"Erreur inattendue lors de la génération du prompt : {e}") - st.session_state.active_generated_prompt = f"ERREUR INATTENDUE - TEMPLATE ORIGINAL :\n---\n{prompt_template_content}" - - st.markdown("---") - if st.session_state.active_generated_prompt: - st.subheader("✅ Prompt Généré (éditable):") - edited_prompt_value = st.text_area( - "Prompt:", - value=st.session_state.active_generated_prompt, - height=200, - key=f"editable_generated_prompt_output_{generator_family}_{generator_use_case}", - label_visibility="collapsed" - ) - if edited_prompt_value != st.session_state.active_generated_prompt: - st.session_state.active_generated_prompt = edited_prompt_value - - col_caption, col_indicator = st.columns([1.8, 0.2]) - with col_caption: - st.caption("Prompt généré (pour relecture et copie manuelle) :") - with col_indicator: - st.markdown("
Copier ici : 👇
", unsafe_allow_html=True) - - if st.session_state.active_generated_prompt: - st.code(st.session_state.active_generated_prompt, language='markdown', line_numbers=True) - else: - st.markdown("*Aucun prompt généré à afficher.*") - - # ⚙️ Paramétrage du Prompt - Configuration Feature (Generator Mode) - should_expand_config = SHOW_CONFIG_SECTION and st.session_state.get('go_to_config_section', False) - if SHOW_CONFIG_SECTION: - with st.expander(f"⚙️ Paramétrage du Prompt: {generator_use_case}", expanded=should_expand_config): - st.info("Section de paramétrage temporairement désactivée.") - - if st.session_state.get('go_to_config_section'): - st.session_state.go_to_config_section = False elif st.session_state.view_mode == "inject_manual": if st.button("⬅️ Retour à l'accueil", key="back_to_accueil_from_inject"): st.session_state.view_mode = "accueil" @@ -1489,7 +1316,6 @@ elif st.session_state.view_mode == "inject_manual": st.caption("Exemple de structure pour un cas d'usage :") json_example_string = """{ "Nom de Mon Nouveau Cas d'Usage": { - "description": "Ce prompt vous aide à réaliser une tâche spécifique. N'oubliez pas d'ajouter le document nécessaire à votre conversation avec l'IA.", "template": "Ceci est le {variable_exemple} pour mon prompt.", "variables": [ { @@ -1552,7 +1378,7 @@ elif st.session_state.view_mode == "inject_manual": if first_new_uc_name is None: first_new_uc_name = uc_name_stripped if successful_injections: - save_editable_prompts_to_local() + save_editable_prompts_to_gist() st.success(f"{len(successful_injections)} cas d'usage injectés avec succès dans '{target_family_name}': {', '.join(successful_injections)}") st.session_state.injection_json_text = "" if first_new_uc_name: @@ -1715,4 +1541,4 @@ elif st.session_state.view_mode == "assistant_creation": # Cette vue gère maint # --- Sidebar Footer --- st.sidebar.markdown("---") -st.sidebar.info(f"Générateur v3.3.6 - © {CURRENT_YEAR} La Poste (démo)") \ No newline at end of file +st.sidebar.info(f"Générateur v3.3.6 - © {CURRENT_YEAR} La Poste (démo)")