diff --git "a/refApp.py" "b/refApp.py" new file mode 100644--- /dev/null +++ "b/refApp.py" @@ -0,0 +1,1718 @@ +import streamlit as st +from datetime import datetime, date +import copy +import json +import requests +import os + +# --- 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 --- +st.markdown(""" + + +""", unsafe_allow_html=True) + +# --- Initial Data Structure & Constants --- +CURRENT_YEAR = datetime.now().year + +# Variable de contrĂŽle pour la section de paramĂ©trage - changez Ă  True pour rĂ©activer +SHOW_CONFIG_SECTION = True +GIST_DATA_FILENAME = "prompt_templates_data_v3.json" +LOCAL_DATA_FILENAME = "prompt_templates_data_v3.json" + +# --- Function to load prompt templates from files --- +def load_prompt_template(filename): + """Load prompt template from file, with fallback to basic template if file not found.""" + try: + with open(filename, 'r', encoding='utf-8') as f: + return f.read() + except FileNotFoundError: + st.warning(f"Fichier de template '{filename}' non trouvĂ©. Utilisation d'un template de base.") + return "# MISSION\nVous ĂȘtes un assistant IA. Veuillez traiter la demande suivante: {problematique}" + except Exception as e: + st.error(f"Erreur lors du chargement du template '{filename}': {e}") + return "# MISSION\nVous ĂȘtes un assistant IA. Veuillez traiter la demande suivante: {problematique}" + +# Load prompt templates from external files +META_PROMPT_FOR_EXTERNAL_LLM_TEMPLATE = load_prompt_template("AIBprompt_creation_template.md") +META_PROMPT_FOR_LLM_AMELIORATION_TEMPLATE = load_prompt_template("AIBprompt_improvement_template.md") + +ASSISTANT_FORM_VARIABLES = [ + {"name": "problematique", "label": "DĂ©crivez le besoin ou la tĂąche que le prompt cible doit rĂ©soudre :", "type": "text_area", "default": "", "height": 100}, + {"name": "doc_source", "label": "Quel(s) types de document(s) sont nĂ©cessaire pour la rĂ©alisation de votre besoin ? (e.g. PDF, e-mail, texte brut -laisser vide si non pertinent-) :", "type": "text_input", "default": ""}, + {"name": "elements_specifiques_a_extraire", "label": "Quelles sont les informations spĂ©cifiques que vous souhaitez identifier / gĂ©nĂ©rer ? (e.g. l'ensemble des ID clients, les clauses du contrat) :", "type": "text_area", "default": "", "height": 100}, + {"name": "format_sortie_desire", "label": "Optionnel : sous quel format voulez vous que le prompt produise une rĂ©ponse ? (e.g. un texte de deux pages, une liste Ă  puces) :", "type": "text_area", "default": "", "height": 75}, + {"name": "public_cible_reponse", "label": "Optionnel : pour quel public cible s'adressera le rĂ©sultat du prompt ? (e.g. des profils techniques, le grand public) :", "type": "text_input", "default": ""}, +] + +def get_default_dates(): + now_iso = datetime.now().isoformat() + return now_iso, now_iso + +INITIAL_PROMPT_TEMPLATES = { + "Propositions commerciales": {}, "Ateliers mĂ©tiers": {}, "Project Management": {} +} +for family, use_cases in INITIAL_PROMPT_TEMPLATES.items(): # Initial cleanup + if isinstance(use_cases, dict): + for uc_name, uc_config in use_cases.items(): + if "is_favorite" in uc_config: # pragma: no cover + del uc_config["is_favorite"] + +# --- Utility Functions (User's original versions, with height fix in _postprocess_after_loading) --- +def parse_default_value(value_str, var_type): + if not value_str: + if var_type == "number_input": return 0.0 + if var_type == "date_input": return datetime.now().date() + return "" + if var_type == "number_input": + try: return float(value_str) + except ValueError: return 0.0 + elif var_type == "date_input": + try: return datetime.strptime(value_str, "%Y-%m-%d").date() + except (ValueError, TypeError): + return value_str if isinstance(value_str, date) else datetime.now().date() + return value_str + +def _preprocess_for_saving(data_to_save): + processed_data = copy.deepcopy(data_to_save) + for family_name in list(processed_data.keys()): + use_cases_in_family = processed_data[family_name] + if not isinstance(use_cases_in_family, dict): # pragma: no cover + st.error(f"DonnĂ©es corrompues (famille non-dict): '{family_name}'. Suppression.") + del processed_data[family_name] + continue + for use_case_name in list(use_cases_in_family.keys()): + config = use_cases_in_family[use_case_name] + if not isinstance(config, dict): # pragma: no cover + st.error(f"DonnĂ©es corrompues (cas d'usage non-dict): '{use_case_name}' dans '{family_name}'. Suppression.") + del processed_data[family_name][use_case_name] + continue + if not isinstance(config.get("variables"), list): + config["variables"] = [] + for var_info in config.get("variables", []): + if isinstance(var_info, dict): + if var_info.get("type") == "date_input" and isinstance(var_info.get("default"), date): + var_info["default"] = var_info["default"].strftime("%Y-%m-%d") + if var_info.get("type") == "number_input": + if "default" in var_info and var_info["default"] is not None: + var_info["default"] = float(var_info["default"]) + if "min_value" in var_info and var_info["min_value"] is not None: + var_info["min_value"] = float(var_info["min_value"]) + if "max_value" in var_info and var_info["max_value"] is not None: + var_info["max_value"] = float(var_info["max_value"]) + if "step" in var_info and var_info["step"] is not None: + var_info["step"] = float(var_info["step"]) + else: + var_info["step"] = 1.0 + # Ensure height for text_area is an int if it exists (it should be already from other functions) + if var_info.get("type") == "text_area": + if "height" in var_info and var_info["height"] is not None: + try: + var_info["height"] = int(var_info["height"]) + except (ValueError, TypeError): # pragma: no cover + var_info["height"] = 100 # Should not happen if data is clean + + config.setdefault("tags", []) + if "is_favorite" in config: # pragma: no cover + del config["is_favorite"] + config.setdefault("usage_count", 0) + config.setdefault("created_at", datetime.now().isoformat()) + config.setdefault("updated_at", datetime.now().isoformat()) + return processed_data + +def _postprocess_after_loading(loaded_data): # User's trusted version + height fix + processed_data = copy.deepcopy(loaded_data) + now_iso = datetime.now().isoformat() + for family_name in list(processed_data.keys()): + use_cases_in_family = processed_data[family_name] + if not isinstance(use_cases_in_family, dict): # pragma: no cover + st.warning(f"DonnĂ©es corrompues (famille non-dict): '{family_name}'. IgnorĂ©e.") + del processed_data[family_name] + continue + for use_case_name in list(use_cases_in_family.keys()): + config = use_cases_in_family[use_case_name] + if not isinstance(config, dict): # pragma: no cover + st.warning(f"DonnĂ©es corrompues (cas d'usage non-dict): '{use_case_name}' dans '{family_name}'. IgnorĂ©.") + del processed_data[family_name][use_case_name] + continue + if not isinstance(config.get("variables"), list): + config["variables"] = [] + for var_info in config.get("variables", []): + if isinstance(var_info, dict): + if var_info.get("type") == "date_input" and isinstance(var_info.get("default"), str): + try: + var_info["default"] = datetime.strptime(var_info["default"], "%Y-%m-%d").date() + except ValueError: + var_info["default"] = datetime.now().date() + if var_info.get("type") == "number_input": + if "default" in var_info and var_info["default"] is not None: + var_info["default"] = float(var_info["default"]) + else: + var_info["default"] = 0.0 + if "min_value" in var_info and var_info["min_value"] is not None: + var_info["min_value"] = float(var_info["min_value"]) + if "max_value" in var_info and var_info["max_value"] is not None: + var_info["max_value"] = float(var_info["max_value"]) + if "step" in var_info and var_info["step"] is not None: + var_info["step"] = float(var_info["step"]) + else: + var_info["step"] = 1.0 + + # --- ADDED ROBUST HEIGHT VALIDATION --- + if var_info.get("type") == "text_area": + height_val = var_info.get("height") + if height_val is not None: + try: + h = int(height_val) + if h >= 68: + var_info["height"] = h + else: + var_info["height"] = 68 # Set to minimum if too small + # st.warning(f"Hauteur pour '{var_info.get('name', 'N/A')}' ajustĂ©e Ă  68px (minimum).") + except (ValueError, TypeError): + var_info["height"] = 100 # Default if invalid + # If height_val was None, 'height' key might not be in var_info, or it's None. + # The st.text_area widget call will handle None by using its internal default. + # Or we can explicitly set a default: + # else: + # var_info["height"] = 100 # Default if not present + + config.setdefault("tags", []) + if "is_favorite" in config: # pragma: no cover + del config["is_favorite"] + config.setdefault("usage_count", 0) + config.setdefault("created_at", now_iso) + config.setdefault("updated_at", now_iso) + if not isinstance(config.get("tags"), list): config["tags"] = [] + return processed_data + +# --- NEW: Simplified function to prepare newly injected use case config --- +def _prepare_newly_injected_use_case_config(uc_config_from_json): + prepared_config = copy.deepcopy(uc_config_from_json) + now_iso_created, now_iso_updated = get_default_dates() + + prepared_config["created_at"] = now_iso_created + prepared_config["updated_at"] = now_iso_updated + prepared_config["usage_count"] = 0 + + if "template" not in prepared_config or not isinstance(prepared_config["template"], str): # pragma: no cover + prepared_config["template"] = "" + st.warning(f"Cas d'usage injectĂ© '{uc_config_from_json.get('name', 'INCONNU')}' sans template valide. Template initialisĂ© Ă  vide.") + + # Ensure description field exists + if "description" not in prepared_config or not isinstance(prepared_config["description"], str): + prepared_config["description"] = "" + + if not isinstance(prepared_config.get("variables"), list): + prepared_config["variables"] = [] + + for var_info in prepared_config.get("variables", []): # Ensure height is valid for text_area + if isinstance(var_info, dict) and var_info.get("type") == "text_area": + height_val = var_info.get("height") + if height_val is not None: + try: + h = int(height_val) + if h >= 68: var_info["height"] = h + else: var_info["height"] = 68 + except (ValueError, TypeError): + var_info["height"] = 100 # Default if invalid type + # If 'height' key is missing, it's fine; widget will use its default. + + if not isinstance(prepared_config.get("tags"), list): + prepared_config["tags"] = [] + else: + prepared_config["tags"] = sorted(list(set(str(tag).strip() for tag in prepared_config["tags"] if str(tag).strip()))) + + if "is_favorite" in prepared_config: # pragma: no cover + del prepared_config["is_favorite"] + + # Ajout pour la gestion des notes + # if "ratings" not in prepared_config or not isinstance(prepared_config["ratings"], list): + # prepared_config["ratings"] = [] + # if "average_rating" not in prepared_config: + # prepared_config["average_rating"] = 0.0 + return prepared_config + +# --- Gist Interaction Functions (User's original versions) --- +def get_local_file_content(): + try: + if os.path.exists(LOCAL_DATA_FILENAME): + with open(LOCAL_DATA_FILENAME, 'r', encoding='utf-8') as f: + content = f.read() + return content if content.strip() else "{}" + else: + st.info(f"Fichier '{LOCAL_DATA_FILENAME}' non trouvĂ©. Initialisation.") + return "{}" + except Exception as e: # pragma: no cover + st.error(f"Erreur de lecture du fichier local: {e}") + return None + +def save_to_local_file(new_content_json_string): + try: + with open(LOCAL_DATA_FILENAME, 'w', encoding='utf-8') as f: + f.write(new_content_json_string) + return True + except Exception as e: # pragma: no cover + st.error(f"Erreur de sauvegarde du fichier local: {e}") + return False + +def save_editable_prompts_to_local(): + if 'editable_prompts' in st.session_state: + data_to_save = _preprocess_for_saving(st.session_state.editable_prompts) + try: + json_string = json.dumps(data_to_save, indent=4, ensure_ascii=False) + if save_to_local_file(json_string): + st.toast("đŸ’Ÿ DonnĂ©es sauvegardĂ©es localement!", icon="đŸ’Ÿ") + else: + st.warning("Sauvegarde locale Ă©chouĂ©e.") + except Exception as e: # pragma: no cover + st.error(f"Erreur prĂ©paration donnĂ©es pour sauvegarde locale: {e}") + +def load_editable_prompts_from_local(): + raw_content = get_local_file_content() + if raw_content: + try: + loaded_data = json.loads(raw_content) + if not loaded_data or not isinstance(loaded_data, dict): + raise ValueError("Contenu fichier local vide ou mal structurĂ©.") + return _postprocess_after_loading(loaded_data) + except (json.JSONDecodeError, TypeError, ValueError) as e: + st.info(f"Erreur chargement fichier local ('{str(e)[:50]}...'). Initialisation avec modĂšles par dĂ©faut.") + else: + st.info("Fichier local vide ou inaccessible. Initialisation avec modĂšles par dĂ©faut.") + initial_data = copy.deepcopy(INITIAL_PROMPT_TEMPLATES) + if raw_content is None or raw_content == "{}": + data_to_save_init = _preprocess_for_saving(initial_data) + try: + json_string_init = json.dumps(data_to_save_init, indent=4, ensure_ascii=False) + if save_to_local_file(json_string_init): + st.info("ModĂšles par dĂ©faut sauvegardĂ©s localement pour initialisation.") + except Exception as e: # pragma: no cover + st.error(f"Erreur sauvegarde initiale locale: {e}") + return initial_data + +# --- ACCESS CODE VERIFICATION --- +# Initialize access control session state +if 'access_granted' not in st.session_state: + st.session_state.access_granted = False +if 'access_code_input' not in st.session_state: + st.session_state.access_code_input = "" + +# Access code popup +if not st.session_state.access_granted: + # Simple CSS for background styling + st.markdown(""" + + """, 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() +if 'view_mode' not in st.session_state: + st.session_state.view_mode = "accueil" # Nouvelle vue par dĂ©faut + +if 'library_selected_family_for_display' not in st.session_state: + available_families = list(st.session_state.editable_prompts.keys()) + st.session_state.library_selected_family_for_display = available_families[0] if available_families else None + +if 'family_selector_edition' not in st.session_state: + available_families = list(st.session_state.editable_prompts.keys()) + st.session_state.family_selector_edition = available_families[0] if available_families else None +if 'use_case_selector_edition' not in st.session_state: st.session_state.use_case_selector_edition = None +if 'editing_variable_info' not in st.session_state: st.session_state.editing_variable_info = None +if 'show_create_new_use_case_form' not in st.session_state: st.session_state.show_create_new_use_case_form = False +if 'force_select_family_name' not in st.session_state: st.session_state.force_select_family_name = None +if 'force_select_use_case_name' not in st.session_state: st.session_state.force_select_use_case_name = None +if 'confirming_delete_details' not in st.session_state: st.session_state.confirming_delete_details = None +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: + st.session_state.injection_json_text = "" + +if 'assistant_form_values' not in st.session_state: + st.session_state.assistant_form_values = {var['name']: var['default'] for var in ASSISTANT_FORM_VARIABLES} +if 'generated_meta_prompt_for_llm' not in st.session_state: + st.session_state.generated_meta_prompt_for_llm = "" + +# NOUVELLES CLÉS POUR L'ASSISTANT UNIFIÉ +if 'assistant_mode' not in st.session_state: + st.session_state.assistant_mode = "creation" # Modes possibles: "creation", "amelioration" +if 'assistant_existing_prompt_value' not in st.session_state: + st.session_state.assistant_existing_prompt_value = "" + +# --- Sidebar Navigation with Tabs --- +st.sidebar.header("Menu Principal") +tab_bibliotheque, tab_injection = st.sidebar.tabs([ + "📚 BibliothĂšque", + "💡 Assistant" +]) + + +# --- Tab: BibliothĂšque (Sidebar content) --- +with tab_bibliotheque: + st.subheader("Explorer la BibliothĂšque de Prompts") + search_col, filter_tag_col = st.columns(2) + with search_col: + st.session_state.library_search_term = st.text_input( + "🔍 Rechercher par mot-clĂ©:", + value=st.session_state.get("library_search_term", ""), + placeholder="Nom, template, variable..." + ) + + all_tags_list = sorted(list(set(tag for family in st.session_state.editable_prompts.values() for uc in family.values() for tag in uc.get("tags", [])))) + with filter_tag_col: + st.session_state.library_selected_tags = st.multiselect( + "đŸ·ïž Filtrer par Tags:", + 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()): + st.info("La bibliothĂšque est vide. Ajoutez des prompts via l'onglet 'Édition'.") + else: + sorted_families_bib = sorted(list(st.session_state.editable_prompts.keys())) + + if not st.session_state.get('library_selected_family_for_display') or \ + st.session_state.library_selected_family_for_display not in sorted_families_bib: + st.session_state.library_selected_family_for_display = sorted_families_bib[0] if sorted_families_bib else None + + st.write("SĂ©lectionner un mĂ©tier Ă  afficher :") + for family_name_bib in sorted_families_bib: + button_key = f"lib_family_btn_{family_name_bib.replace(' ', '_').replace('&', '_')}" + is_selected_family = (st.session_state.library_selected_family_for_display == family_name_bib) + if st.button( + family_name_bib, + key=button_key, + use_container_width=True, + type="primary" if is_selected_family else "secondary" + ): + if st.session_state.library_selected_family_for_display != family_name_bib: + st.session_state.library_selected_family_for_display = family_name_bib + st.session_state.view_mode = "library" + st.rerun() + st.markdown("---") + +# --- Tab: Injection (Sidebar content) --- +with tab_injection: + st.subheader("Assistant & Injection") + st.markdown("Utilisez l'assistant pour prĂ©parer un prompt systĂšme ou injectez des cas d'usage en format JSON.") + # MODIFICATION DU BOUTON EXISTANT + if st.button("✹ Assistant Prompt SystĂšme", key="start_assistant_unified_btn", use_container_width=True): # Nom du bouton mis Ă  jour + st.session_state.view_mode = "assistant_creation" + # RĂ©initialiser au mode "creation" par dĂ©faut et vider les champs des deux modes potentiels + st.session_state.assistant_mode = "creation" + st.session_state.assistant_form_values = {var['name']: var['default'] for var in ASSISTANT_FORM_VARIABLES} + st.session_state.assistant_existing_prompt_value = "" + st.session_state.generated_meta_prompt_for_llm = "" # Le mĂ©ta-prompt gĂ©nĂ©rĂ© est commun + st.rerun() + + if st.button("💉 Injecter JSON Manuellement", key="start_manual_injection_btn", use_container_width=True): + st.session_state.view_mode = "inject_manual" + st.session_state.injection_selected_family = None + st.session_state.injection_json_text = "" + st.session_state.generated_meta_prompt_for_llm = "" # Aussi rĂ©initialiser ici + 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.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 ! + + **Qu'est-ce qu'un "prompt" ?** + Imaginez donner des instructions Ă  un assistant virtuel intelligent, mais qui a besoin de consignes claires. Un "prompt", c'est simplement cette instruction, cette question ou cette consigne que vous formulez Ă  l'IA. + Plus votre instruction est bien pensĂ©e, plus l'IA vous fournira une rĂ©ponse utile et pertinente. + + **Que pouvez-vous faire avec cette application ?** + + Cet outil est conçu pour vous simplifier la vie, que vous soyez novice ou plus expĂ©rimentĂ© : + + * **DĂ©couvrir et utiliser des modĂšles d'instructions prĂȘts Ă  l'emploi** : Explorez une collection de "prompts" dĂ©jĂ  conçus pour diverses tĂąches (comme rĂ©diger un email, rĂ©sumer un document, analyser une situation, etc.). Vous pourrez les utiliser tels quels ou les adapter facilement. + * **CrĂ©er vos propres instructions sur mesure** : Vous avez une idĂ©e prĂ©cise en tĂȘte ? Notre assistant vous guide pas Ă  pas pour construire le "prompt" parfait, mĂȘme si vous n'avez aucune connaissance technique. L'objectif est de transformer votre besoin en une instruction claire pour l'IA. + * **Organiser et amĂ©liorer vos instructions** : Conservez vos meilleurs "prompts", modifiez-les et perfectionnez-les au fil du temps. + + En bref, cet outil vous aide Ă  formuler les meilleures demandes possibles aux IA pour qu'elles deviennent de vĂ©ritables alliĂ©es dans votre travail ou vos projets. + """) + + cols_accueil = st.columns(2) + with cols_accueil[0]: + if st.button("📚 Je souhaite utiliser / modifier un prompt existant", use_container_width=True, type="primary"): + st.session_state.view_mode = "select_family_for_library" + st.rerun() + with cols_accueil[1]: + if st.button("✹ Je souhaite crĂ©er un prompt Ă  partir de mon besoin", use_container_width=True, type="primary"): + st.session_state.view_mode = "assistant_creation" + # RĂ©initialiser les valeurs du formulaire de l'assistant et le prompt gĂ©nĂ©rĂ© + st.session_state.assistant_form_values = {var['name']: var['default'] for var in ASSISTANT_FORM_VARIABLES} + st.session_state.generated_meta_prompt_for_llm = "" + st.rerun() + +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.markdown("---") + + # 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("---") + + # 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" + st.rerun() + if not library_family_to_display: + st.info("Veuillez sĂ©lectionner un mĂ©tier dans la barre latĂ©rale (onglet BibliothĂšque) pour afficher les prompts.") + available_families_main_display = list(st.session_state.editable_prompts.keys()) + if available_families_main_display: + st.session_state.library_selected_family_for_display = available_families_main_display[0] + st.rerun() + elif not any(st.session_state.editable_prompts.values()): + st.warning("Aucun mĂ©tier de cas d'usage n'est configurĂ©e. CrĂ©ez-en via l'onglet 'Édition'.") + elif library_family_to_display in st.session_state.editable_prompts: + st.header(f"BibliothĂšque - mĂ©tier : {library_family_to_display}") + use_cases_in_family_display = st.session_state.editable_prompts[library_family_to_display] + filtered_use_cases = {} + search_term_lib = st.session_state.get("library_search_term", "").strip().lower() + selected_tags_lib = st.session_state.get("library_selected_tags", []) + if use_cases_in_family_display: + for uc_name, uc_config in use_cases_in_family_display.items(): + 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: filtered_use_cases[uc_name] = uc_config + if not filtered_use_cases: + 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] + template_display = prompt_config_display.get("template", "_Template non dĂ©fini._") + exp_title = f"{use_case_name_display}" + if prompt_config_display.get("usage_count", 0) > 0: exp_title += f" (UtilisĂ© {prompt_config_display.get('usage_count')} fois)" + with st.expander(exp_title, expanded=False): + + tags_display = prompt_config_display.get("tags", []) + 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')}") + + 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"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() + 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() + 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()) + if not available_families_check : st.warning("La bibliothĂšque est entiĂšrement vide. Veuillez crĂ©er des mĂ©tiers et des prompts.") + +elif st.session_state.view_mode == "edit": + current_family_of_edited_prompt = st.session_state.get('family_selector_edition') # ou 'mĂ©tier_selector_edition' si vous avez renommĂ© cette clĂ© de session_state + if st.button(f"âŹ…ïž Retour Ă  la bibliothĂšque ({current_family_of_edited_prompt or 'MĂ©tier'})", key="back_to_library_from_edit"): + if current_family_of_edited_prompt: + st.session_state.library_selected_family_for_display = current_family_of_edited_prompt + st.session_state.view_mode = "library" + st.rerun() + if not final_selected_family_edition : st.info("SĂ©lectionnez un mĂ©tier dans la barre latĂ©rale (onglet Édition) ou crĂ©ez-en un pour commencer.") + elif not final_selected_use_case_edition: st.info(f"SĂ©lectionnez un cas d'usage dans le mĂ©tier '{final_selected_family_edition}' ou crĂ©ez-en un nouveau pour commencer.") + elif final_selected_family_edition in st.session_state.editable_prompts and final_selected_use_case_edition in st.session_state.editable_prompts[final_selected_family_edition]: + 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}*") + 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 = [] + 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_{final_selected_family_edition}_{final_selected_use_case_edition}_{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 # pragma: no cover + 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(): # gen_form_values vient de votre formulaire + if v_val is None: + # On ne met pas les valeurs None dans le dictionnaire, + # donc les placeholders correspondants ne seront pas remplacĂ©s (comportement original) + continue + + if isinstance(v_val, date): + processed_values_for_template[k] = v_val.strftime("%d/%m/%Y") + elif isinstance(v_val, float): # On regroupe ici tous les traitements pour les floats + if v_val.is_integer(): # Si le float est un entier (ex: 50.0) + processed_values_for_template[k] = str(int(v_val)) # Convertir en "50" + else: # S'il s'agit d'un float avec des dĂ©cimales (ex: 0.125000...) + processed_values_for_template[k] = f"{v_val:.2f}" # Formater avec 2 dĂ©cimales + else: # Pour tous les autres types (str, bool, etc. qui ne sont ni date ni float) + processed_values_for_template[k] = str(v_val) + + final_vals_for_prompt = processed_values_for_template # final_vals_for_prompt contient maintenant des chaĂźnes + + try: + prompt_template_content = current_prompt_config.get("template", "") + processed_template = prompt_template_content + + # 1. Remplacer les variables connues par Streamlit (celles du formulaire) + # Trier par longueur de clĂ© (descendant) pour Ă©viter les substitutions partielles + # (ex: remplacer {jour_semaine} avant {jour}) + 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}}}" + # Remplacer uniquement les placeholders exacts et simples + processed_template = processed_template.replace(placeholder_streamlit, str(var_value)) + + # 2. Convertir les doubles accolades (pour le LLM final) en simples accolades + # Ceci suppose que le template original (venant du JSON) utilisait bien {{...}} + # pour les placeholders destinĂ©s au LLM final. + formatted_template_content = processed_template.replace("{{", "{").replace("}}", "}") + + use_case_title = final_selected_use_case_edition + 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: # 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 + st.session_state.active_generated_prompt = f"ERREUR INATTENDUE - TEMPLATE ORIGINAL :\n---\n{prompt_template_content}" # pragma: no cover + 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_{final_selected_family_edition}_{final_selected_use_case_edition}", label_visibility="collapsed") + if edited_prompt_value != st.session_state.active_generated_prompt: + st.session_state.active_generated_prompt = edited_prompt_value # pragma: no cover + col_caption, col_indicator = st.columns([1.8, 0.2]) # Ajustez les proportions si nĂ©cessaire + 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.*") + + st.markdown("---") # Un petit sĂ©parateur + + 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}'.") + 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("---") + + else: + if not final_selected_family_edition: + st.info("Veuillez sĂ©lectionner un mĂ©tier dans la barre latĂ©rale (onglet Édition) pour commencer.") + elif not final_selected_use_case_edition: + st.info(f"Veuillez sĂ©lectionner un cas d'usage pour le mĂ©tier '{final_selected_family_edition}' ou en crĂ©er un.") + else: + 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" + st.rerun() + st.header("💉 Injection Manuelle de Cas d'Usage JSON") + st.markdown("""Collez ici un ou plusieurs cas d'usage au format JSON. Le JSON doit ĂȘtre un dictionnaire oĂč chaque clĂ© est le nom du nouveau cas d'usage, et la valeur est sa configuration.""") + 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": [ + { + "name": "variable_exemple", + "label": "Variable d'Exemple", + "type": "text_input", + "default": "texte par dĂ©faut" + } + ], + "tags": ["nouveau", "exemple"] + } +}""" + st.code(json_example_string, language="json") + available_families_for_injection = list(st.session_state.editable_prompts.keys()) + if not available_families_for_injection: + st.warning("Aucun mĂ©tier n'existe. Veuillez d'abord crĂ©er un mĂ©tier via l'onglet 'Édition'.") + else: + selected_family_for_injection = st.selectbox("Choisissez le mĂ©tier de destination pour l'injection :", options=[""] + available_families_for_injection, index=0, key="injection_family_selector") + st.session_state.injection_selected_family = selected_family_for_injection if selected_family_for_injection else None + if st.session_state.injection_selected_family: + st.subheader(f"Injecter dans le mĂ©tier : {st.session_state.injection_selected_family}") + st.session_state.injection_json_text = st.text_area("Collez le JSON des cas d'usage ici :", value=st.session_state.get("injection_json_text", ""), height=300, key="injection_json_input") + if st.button("➕ Injecter les Cas d'Usage", key="submit_injection_btn"): + if not st.session_state.injection_json_text.strip(): + st.error("La zone de texte JSON est vide.") + else: + try: + injected_data = json.loads(st.session_state.injection_json_text) + if not isinstance(injected_data, dict): + st.error("Le JSON fourni doit ĂȘtre un dictionnaire (objet JSON).") + else: + target_family_name = st.session_state.injection_selected_family + if target_family_name not in st.session_state.editable_prompts: + st.error(f"Le mĂ©tier de destination '{target_family_name}' n'existe plus ou n'a pas Ă©tĂ© correctement sĂ©lectionnĂ©e.") + else: + family_prompts = st.session_state.editable_prompts[target_family_name] + successful_injections = [] + failed_injections = [] + first_new_uc_name = None + for uc_name, uc_config_json in injected_data.items(): + uc_name_stripped = uc_name.strip() + if not uc_name_stripped: + failed_injections.append(f"Nom de cas d'usage vide ignorĂ©.") + continue + if not isinstance(uc_config_json, dict) or "template" not in uc_config_json: + failed_injections.append(f"'{uc_name_stripped}': Configuration invalide ou template manquant.") + continue + if uc_name_stripped in family_prompts: + st.warning(f"Le cas d'usage '{uc_name_stripped}' existe dĂ©jĂ  dans le mĂ©tier '{target_family_name}'. Il a Ă©tĂ© ignorĂ©.") + failed_injections.append(f"'{uc_name_stripped}': Existe dĂ©jĂ , ignorĂ©.") + continue + + prepared_uc_config = _prepare_newly_injected_use_case_config(uc_config_json) + + if not prepared_uc_config.get("template"): + failed_injections.append(f"'{uc_name_stripped}': Template invalide aprĂšs traitement.") + continue + family_prompts[uc_name_stripped] = prepared_uc_config + successful_injections.append(uc_name_stripped) + if first_new_uc_name is None: + first_new_uc_name = uc_name_stripped + if successful_injections: + save_editable_prompts_to_local() + 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: + st.session_state.view_mode = "edit" + st.session_state.force_select_family_name = target_family_name + st.session_state.force_select_use_case_name = first_new_uc_name + st.session_state.go_to_config_section = True + st.session_state.active_generated_prompt = "" # <--- AJOUTEZ CETTE LIGNE ICI + st.rerun() + if failed_injections: + for fail_msg in failed_injections: + st.error(f"Échec d'injection : {fail_msg}") + if not successful_injections and not failed_injections: + st.info("Aucun cas d'usage n'a Ă©tĂ© trouvĂ© dans le JSON fourni ou tous Ă©taient vides/invalides.") + except json.JSONDecodeError as e: + st.error(f"Erreur de parsing JSON : {e}") + except Exception as e: + st.error(f"Une erreur inattendue est survenue lors de l'injection : {e}") # pragma: no cover + else: + st.info("Veuillez sĂ©lectionner un mĂ©tier de destination pour commencer l'injection.") + +elif st.session_state.view_mode == "assistant_creation": # Cette vue gĂšre maintenant les deux modes + if st.button("âŹ…ïž Retour Ă  l'accueil", key="back_to_accueil_from_assistant_unified"): + st.session_state.view_mode = "accueil" + st.rerun() + st.header("✹ Assistant Prompt SystĂšme") + + # S'assurer que assistant_mode a une valeur initiale valide si elle n'est pas dĂ©jĂ  dĂ©finie + if 'assistant_mode' not in st.session_state: + st.session_state.assistant_mode = "creation" + + mode_options_labels = { + "creation": "🆕 CrĂ©er un nouveau prompt systĂšme", + "amelioration": "🚀 AmĂ©liorer un prompt existant" + } + + # Utiliser l'index pour que st.radio se souvienne de la sĂ©lection via st.session_state.assistant_mode + current_mode_index = 0 if st.session_state.assistant_mode == "creation" else 1 + + selected_mode_key = st.radio( + "Que souhaitez-vous faire ?", + options=list(mode_options_labels.keys()), + format_func=lambda key: mode_options_labels[key], + index=current_mode_index, + key="assistant_mode_radio_selector" # ClĂ© unique pour le widget radio + ) + + # Si le mode sĂ©lectionnĂ© via le radio a changĂ©, mettre Ă  jour st.session_state et rerun pour rafraĂźchir le formulaire + if selected_mode_key != st.session_state.assistant_mode: + st.session_state.assistant_mode = selected_mode_key + st.session_state.generated_meta_prompt_for_llm = "" # Vider le prompt gĂ©nĂ©rĂ© car le mode a changĂ© + # Optionnel: vider les valeurs des formulaires lors du changement de mode pour Ă©viter confusion + # st.session_state.assistant_form_values = {var['name']: var['default'] for var in ASSISTANT_FORM_VARIABLES} + # st.session_state.assistant_existing_prompt_value = "" + st.rerun() + + if st.session_state.assistant_mode == "creation": + st.markdown("DĂ©crivez votre besoin pour que l'assistant gĂ©nĂšre une instruction dĂ©taillĂ©e. Vous donnerez cette instruction Ă  LaPoste GPT qui, en retour, produira les Ă©lĂ©ments de votre cas d'usage (prompt systĂšme, variables, etc.).") + with st.form(key="assistant_creation_form_std"): + # Initialiser current_form_input_values avec les valeurs de session_state ou les valeurs par dĂ©faut + # pour que les champs du formulaire soient prĂ©-remplis correctement. + temp_form_values = {} + for var_info in ASSISTANT_FORM_VARIABLES: + field_key = f"assistant_form_{var_info['name']}" + # Utilise la valeur de session_state pour ce champ ou la valeur par dĂ©faut si non trouvĂ©e + value_for_widget = st.session_state.assistant_form_values.get(var_info['name'], var_info['default']) + + if var_info["type"] == "text_input": + temp_form_values[var_info["name"]] = st.text_input( + var_info["label"], value=value_for_widget, key=field_key + ) + elif var_info["type"] == "text_area": + temp_form_values[var_info["name"]] = st.text_area( + var_info["label"], value=value_for_widget, height=var_info.get("height", 100), key=field_key + ) + elif var_info["type"] == "number_input": # Assurez-vous que ce cas est gĂ©rĂ© si vous l'avez + try: + num_value_for_widget = float(value_for_widget if value_for_widget else var_info["default"]) + except (ValueError, TypeError): + num_value_for_widget = float(var_info["default"]) + temp_form_values[var_info["name"]] = st.number_input( + var_info["label"], + value=num_value_for_widget, + min_value=float(var_info.get("min_value")) if var_info.get("min_value") is not None else None, + max_value=float(var_info.get("max_value")) if var_info.get("max_value") is not None else None, + step=float(var_info.get("step", 1.0)), + key=field_key, + format="%g" # ou un autre format si nĂ©cessaire + ) + submitted_assistant_form = st.form_submit_button("📝 GĂ©nĂ©rer l'instruction de crĂ©ation") + + if submitted_assistant_form: + st.session_state.assistant_form_values = temp_form_values.copy() # Sauvegarde les valeurs actuelles du formulaire + try: + # VĂ©rifier si tous les champs requis pour ce template sont remplis (si nĂ©cessaire) + populated_meta_prompt = META_PROMPT_FOR_EXTERNAL_LLM_TEMPLATE.format(**st.session_state.assistant_form_values) + st.session_state.generated_meta_prompt_for_llm = populated_meta_prompt + st.success("Instruction de crĂ©ation gĂ©nĂ©rĂ©e !") + except KeyError as e: + st.error(f"Erreur lors de la construction de l'instruction. ClĂ© de formatage manquante : {e}.") + st.session_state.generated_meta_prompt_for_llm = "" + except Exception as e: + st.error(f"Une erreur inattendue est survenue : {e}") + st.session_state.generated_meta_prompt_for_llm = "" + + elif st.session_state.assistant_mode == "amelioration": + st.markdown("Collez votre prompt existant. L'assistant gĂ©nĂ©rera une instruction pour LaPoste GPT afin de transformer votre prompt en un cas d'usage structurĂ© et amĂ©liorable pour cette application.") + with st.form(key="assistant_amelioration_form_unified"): + # Utilise la valeur de session_state pour ce champ + prompt_existant_input_val = st.text_area( + "Collez votre prompt existant ici :", + value=st.session_state.assistant_existing_prompt_value, + height=300, + key="assistant_form_prompt_existant_unified" + ) + submitted_assistant_amelioration_form = st.form_submit_button("📝 GĂ©nĂ©rer l'instruction d'amĂ©lioration") + + if submitted_assistant_amelioration_form: + st.session_state.assistant_existing_prompt_value = prompt_existant_input_val # Sauvegarde la valeur soumise + if not prompt_existant_input_val.strip(): + st.error("Veuillez coller un prompt existant dans la zone de texte.") + st.session_state.generated_meta_prompt_for_llm = "" + else: + try: + populated_meta_prompt_amelioration = META_PROMPT_FOR_LLM_AMELIORATION_TEMPLATE.format( + prompt_existant=prompt_existant_input_val # Utiliser la valeur actuelle du champ + ) + st.session_state.generated_meta_prompt_for_llm = populated_meta_prompt_amelioration + st.success("Instruction d'amĂ©lioration gĂ©nĂ©rĂ©e !") + except KeyError as e: + st.error(f"Erreur lors de la construction de l'instruction. ClĂ© de formatage manquante : {e}.") + st.session_state.generated_meta_prompt_for_llm = "" + except Exception as e: + st.error(f"Une erreur inattendue est survenue : {e}") + st.session_state.generated_meta_prompt_for_llm = "" + + # Affichage commun du mĂ©ta-prompt gĂ©nĂ©rĂ© (qu'il vienne de la crĂ©ation ou de l'amĂ©lioration) + if st.session_state.generated_meta_prompt_for_llm: + col_subheader_assist, col_indicator_assist = st.columns([0.85, 0.15]) + with col_subheader_assist: + st.subheader("📋 Instruction GĂ©nĂ©rĂ©e (Ă  coller dans LaPosteGPT) :") + with col_indicator_assist: + st.markdown("
Copier ici : 👇
", unsafe_allow_html=True) + + st.code(st.session_state.generated_meta_prompt_for_llm, language='markdown', line_numbers=True) + st.caption("Utilisez l'icĂŽne en haut Ă  droite du bloc de code pour copier l'instruction.", unsafe_allow_html=True) + st.markdown("---") + st.info("Une fois que LaPoste GPT (ou votre LLM externe) a gĂ©nĂ©rĂ© le JSON basĂ© sur cette instruction, copiez ce JSON et utilisez le bouton \"💉 Injecter JSON Manuellement\" (disponible aussi dans l'onglet Assistant du menu) pour l'ajouter Ă  votre atelier.") + if st.button("💉 Injecter JSON Manuellement", key="prepare_inject_from_assistant_unified_btn", use_container_width=True, type="primary"): + st.session_state.view_mode = "inject_manual" + st.session_state.injection_selected_family = None + st.session_state.injection_json_text = "" + st.toast("Collez le JSON gĂ©nĂ©rĂ© par le LLM et sĂ©lectionnez un mĂ©tier de destination.", icon="💡") + st.rerun() + if not any(st.session_state.editable_prompts.values()): # pragma: no cover + st.warning("Aucun groupement de cas d'usage mĂ©tier n'est configurĂ©e. Veuillez en crĂ©er une via l'onglet 'Édition' ou vĂ©rifier votre Gist.") + elif st.session_state.view_mode not in ["library", "edit", "inject_manual", "assistant_creation"]: # pragma: no cover + st.session_state.view_mode = "library" if list(st.session_state.editable_prompts.keys()) else "edit" + st.rerun() + +# --- 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