Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -4,6 +4,54 @@ import copy
|
|
| 4 |
import json
|
| 5 |
import requests
|
| 6 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
# --- PAGE CONFIGURATION (MUST BE THE FIRST STREAMLIT COMMAND) ---
|
| 9 |
# Fixed deployment issue
|
|
@@ -14,45 +62,36 @@ st.markdown("""
|
|
| 14 |
<style>
|
| 15 |
/* Cible le bouton spécifique que vous avez identifié */
|
| 16 |
button[data-testid="stBaseButton-headerNoPadding"]::after {
|
| 17 |
-
content: " Menu";
|
| 18 |
-
margin-left: 8px;
|
| 19 |
-
font-size: 0.9em;
|
| 20 |
-
vertical-align: middle;
|
| 21 |
-
color: inherit;
|
| 22 |
-
font-weight: normal;
|
| 23 |
-
display: inline-flex;
|
| 24 |
align-items: center;
|
| 25 |
}
|
| 26 |
div[data-testid="stCodeBlock"] pre,
|
| 27 |
-
pre.st-emotion-cache-1nqbjoj
|
| 28 |
-
{
|
| 29 |
-
|
| 30 |
max-height: 520px !important;
|
| 31 |
overflow-y: auto !important;
|
| 32 |
font-size: 0.875em !important;
|
| 33 |
-
|
| 34 |
-
display: block !important;
|
| 35 |
visibility: visible !important;
|
| 36 |
opacity: 1 !important;
|
| 37 |
}
|
| 38 |
-
|
| 39 |
-
/* Cible le div conteneur direct à l'intérieur de stCodeBlock s'il existe et gère le scroll */
|
| 40 |
div[data-testid="stCodeBlock"] > div:first-child {
|
| 41 |
-
|
| 42 |
-
max-height: 520px !important; /* Correspond à la valeur du pre ci-dessus */
|
| 43 |
overflow-y: auto !important;
|
| 44 |
-
|
| 45 |
visibility: visible !important;
|
| 46 |
opacity: 1 !important;
|
| 47 |
}
|
| 48 |
-
|
| 49 |
-
/* Si le div interne au <pre> doit gérer le scroll */
|
| 50 |
pre.st-emotion-cache-1nqbjoj > div[style*="background-color: transparent;"] {
|
| 51 |
-
height: auto !important;
|
| 52 |
-
max-height: 100% !important;
|
| 53 |
overflow-y: auto !important;
|
| 54 |
}
|
| 55 |
-
/* === NOUVELLES RÈGLES POUR L'ICÔNE DE COPIE DE ST.CODE === */
|
| 56 |
button[data-testid="stCodeCopyButton"] {
|
| 57 |
opacity: 0.85 !important;
|
| 58 |
visibility: visible !important;
|
|
@@ -61,240 +100,88 @@ st.markdown("""
|
|
| 61 |
border-radius: 4px !important;
|
| 62 |
padding: 3px 5px !important;
|
| 63 |
transition: opacity 0.15s ease-in-out, background-color 0.15s ease-in-out;
|
| 64 |
-
/* top: 2px !important; */
|
| 65 |
-
/* right: 2px !important; */
|
| 66 |
}
|
| 67 |
-
|
| 68 |
button[data-testid="stCodeCopyButton"]:hover {
|
| 69 |
opacity: 1 !important;
|
| 70 |
background-color: #e6e8eb !important;
|
| 71 |
border-color: #b0b0b0 !important;
|
| 72 |
}
|
| 73 |
-
|
| 74 |
button[data-testid="stCodeCopyButton"] svg {
|
| 75 |
-
transform: scale(1.2);
|
| 76 |
vertical-align: middle;
|
| 77 |
}
|
| 78 |
-
|
| 79 |
-
/* === SOLUTION POUR COMPRESSION LATERALE DE LA SIDEBAR === */
|
| 80 |
-
/* Force le contenu principal à se comprimer au lieu d'être décalé */
|
| 81 |
section[data-testid="stSidebar"] {
|
| 82 |
width: 31.5rem !important;
|
| 83 |
min-width: 31.5rem !important;
|
| 84 |
max-width: 31.5rem !important;
|
| 85 |
}
|
| 86 |
-
|
| 87 |
-
/* RÉDUIRE LA SIDEBAR QUAND FERMÉE */
|
| 88 |
section[data-testid="stSidebar"][aria-expanded="false"] {
|
| 89 |
width: 0rem !important;
|
| 90 |
min-width: 0rem !important;
|
| 91 |
max-width: 0rem !important;
|
| 92 |
overflow: hidden !important;
|
| 93 |
}
|
| 94 |
-
|
| 95 |
-
/* Ajustement du conteneur principal pour la compression */
|
| 96 |
-
.main .block-container {
|
| 97 |
-
max-width: calc(100vw - 31.5rem) !important;
|
| 98 |
-
width: calc(100vw - 31.5rem) !important;
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
/* Quand la sidebar est fermée, reprendre toute la largeur */
|
| 102 |
-
section[data-testid="stSidebar"][aria-expanded="false"] ~ .main .block-container,
|
| 103 |
-
section[data-testid="stSidebar"]:not([aria-expanded="true"]) ~ .main .block-container {
|
| 104 |
-
max-width: 100vw !important;
|
| 105 |
-
width: 100vw !important;
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
/* Alternative pour cibler via l'état collapsed */
|
| 109 |
-
.main .block-container {
|
| 110 |
-
transition: width 0.3s ease, max-width 0.3s ease !important;
|
| 111 |
-
}
|
| 112 |
-
|
| 113 |
-
/* PADDING SPECIFIQUE POUR INTERPRO1_LIGHT */
|
| 114 |
[data-testid="stMainBlockContainer"] {
|
| 115 |
padding-top: 3rem !important;
|
| 116 |
}
|
| 117 |
-
|
| 118 |
.css-1d391kg, .css-18e3th9 {
|
| 119 |
padding-top: 3rem !important;
|
| 120 |
}
|
| 121 |
-
|
| 122 |
-
/* REDUCTION SPECIFIQUE POUR LA PAGE D'ACCUEIL UNIQUEMENT */
|
| 123 |
h1[data-testid="stHeading"]:first-of-type {
|
| 124 |
margin-top: -2rem !important;
|
| 125 |
padding-top: 0rem !important;
|
| 126 |
}
|
| 127 |
-
|
| 128 |
h1:contains("Bienvenue dans votre laboratoire") {
|
| 129 |
margin-top: -2rem !important;
|
| 130 |
padding-top: 0rem !important;
|
| 131 |
}
|
| 132 |
-
|
| 133 |
-
/* Responsive: sur petits écrans, garder le comportement normal */
|
| 134 |
@media (max-width: 768px) {
|
| 135 |
.main .block-container {
|
| 136 |
max-width: 100vw !important;
|
| 137 |
width: 100vw !important;
|
| 138 |
}
|
| 139 |
}
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
section[data-testid="stSidebar"][aria-expanded="true"] ~ .main [data-testid="stCodeBlock"],
|
| 153 |
-
section[data-testid="stSidebar"][aria-expanded="true"] ~ .main div[data-testid="stCodeBlock"],
|
| 154 |
-
section[data-testid="stSidebar"][aria-expanded="true"] ~ .main .stCodeBlock {
|
| 155 |
-
max-width: calc(100vw - 31.5rem) !important;
|
| 156 |
-
width: calc(100vw - 31.5rem) !important;
|
| 157 |
}
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
[data-testid="stCodeBlock"] > div,
|
| 161 |
-
[data-testid="stCodeBlock"] pre,
|
| 162 |
-
[data-testid="stCodeBlock"] code {
|
| 163 |
max-width: 100% !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
width: 100% !important;
|
| 165 |
-
|
| 166 |
-
overflow
|
|
|
|
|
|
|
| 167 |
}
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
section[data-testid="stSidebar"][aria-expanded="true"] ~ .main [data-testid="stCodeBlock"] pre,
|
| 172 |
-
section[data-testid="stSidebar"][aria-expanded="true"] ~ .main [data-testid="stCodeBlock"] code {
|
| 173 |
-
max-width: calc(100vw - 31.5rem) !important;
|
| 174 |
-
width: calc(100vw - 31.5rem) !important;
|
| 175 |
}
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
// Force la détection de l'état de la sidebar
|
| 180 |
-
function checkSidebarState() {
|
| 181 |
-
const sidebar = document.querySelector('section[data-testid="stSidebar"]');
|
| 182 |
-
const mainContent = document.querySelector('.main .block-container');
|
| 183 |
-
|
| 184 |
-
if (sidebar && mainContent) {
|
| 185 |
-
const isExpanded = sidebar.getAttribute('aria-expanded') === 'true';
|
| 186 |
-
|
| 187 |
-
if (isExpanded) {
|
| 188 |
-
mainContent.style.maxWidth = 'calc(100vw - 31.5rem)';
|
| 189 |
-
mainContent.style.width = 'calc(100vw - 31.5rem)';
|
| 190 |
-
} else {
|
| 191 |
-
mainContent.style.maxWidth = '100vw';
|
| 192 |
-
mainContent.style.width = '100vw';
|
| 193 |
-
}
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
-
// FORCE SPÉCIAL POUR st.code() - Le vrai problème
|
| 197 |
-
const codeBlocks = document.querySelectorAll('[data-testid="stCodeBlock"], div[data-testid="stCodeBlock"], .stCodeBlock');
|
| 198 |
-
codeBlocks.forEach(block => {
|
| 199 |
-
if (sidebar && sidebar.getAttribute('aria-expanded') === 'true') {
|
| 200 |
-
// Sidebar ouverte - compresser
|
| 201 |
-
block.style.maxWidth = 'calc(100vw - 31.5rem)';
|
| 202 |
-
block.style.width = 'calc(100vw - 31.5rem)';
|
| 203 |
-
block.style.boxSizing = 'border-box';
|
| 204 |
-
|
| 205 |
-
// Forcer aussi sur les éléments internes
|
| 206 |
-
const innerElements = block.querySelectorAll('div, pre, code');
|
| 207 |
-
innerElements.forEach(inner => {
|
| 208 |
-
inner.style.maxWidth = '100%';
|
| 209 |
-
inner.style.width = '100%';
|
| 210 |
-
inner.style.overflowX = 'auto';
|
| 211 |
-
});
|
| 212 |
-
} else {
|
| 213 |
-
// Sidebar fermée - pleine largeur
|
| 214 |
-
block.style.maxWidth = '100vw';
|
| 215 |
-
block.style.width = '100vw';
|
| 216 |
-
|
| 217 |
-
const innerElements = block.querySelectorAll('div, pre, code');
|
| 218 |
-
innerElements.forEach(inner => {
|
| 219 |
-
inner.style.maxWidth = '100%';
|
| 220 |
-
inner.style.width = '100%';
|
| 221 |
-
});
|
| 222 |
-
}
|
| 223 |
-
});
|
| 224 |
}
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
// Démarrer l'observation quand le DOM est prêt
|
| 230 |
-
document.addEventListener('DOMContentLoaded', function() {
|
| 231 |
-
const sidebar = document.querySelector('section[data-testid="stSidebar"]');
|
| 232 |
-
if (sidebar) {
|
| 233 |
-
observer.observe(sidebar, { attributes: true, attributeFilter: ['aria-expanded'] });
|
| 234 |
-
}
|
| 235 |
-
checkSidebarState(); // Check initial state
|
| 236 |
-
});
|
| 237 |
-
|
| 238 |
-
// Observer pour changements DOM (génération de contenu)
|
| 239 |
-
const contentObserver = new MutationObserver(function(mutations) {
|
| 240 |
-
mutations.forEach(function(mutation) {
|
| 241 |
-
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
| 242 |
-
// Du nouveau contenu a été ajouté, re-vérifier l'état
|
| 243 |
-
setTimeout(checkSidebarState, 100);
|
| 244 |
-
}
|
| 245 |
-
});
|
| 246 |
-
});
|
| 247 |
-
|
| 248 |
-
// Observer le conteneur principal pour changements
|
| 249 |
-
document.addEventListener('DOMContentLoaded', function() {
|
| 250 |
-
const main = document.querySelector('.main');
|
| 251 |
-
if (main) {
|
| 252 |
-
contentObserver.observe(main, { childList: true, subtree: true });
|
| 253 |
-
}
|
| 254 |
-
});
|
| 255 |
-
|
| 256 |
-
// Fallback: check périodique
|
| 257 |
-
setInterval(checkSidebarState, 500);
|
| 258 |
-
|
| 259 |
-
// Disable Enter key form submission
|
| 260 |
-
function disableEnterSubmit() {
|
| 261 |
-
const forms = document.querySelectorAll('form[data-testid="stForm"]');
|
| 262 |
-
forms.forEach(form => {
|
| 263 |
-
const inputs = form.querySelectorAll('input[type="text"], textarea');
|
| 264 |
-
inputs.forEach(input => {
|
| 265 |
-
input.addEventListener('keydown', function(event) {
|
| 266 |
-
if (event.key === 'Enter' && !event.shiftKey) {
|
| 267 |
-
event.preventDefault();
|
| 268 |
-
return false;
|
| 269 |
-
}
|
| 270 |
-
});
|
| 271 |
-
});
|
| 272 |
-
});
|
| 273 |
}
|
| 274 |
-
|
| 275 |
-
// Apply on DOM ready and on content changes
|
| 276 |
-
document.addEventListener('DOMContentLoaded', disableEnterSubmit);
|
| 277 |
-
|
| 278 |
-
// Observer for new forms being added dynamically
|
| 279 |
-
const formObserver = new MutationObserver(function(mutations) {
|
| 280 |
-
mutations.forEach(function(mutation) {
|
| 281 |
-
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
| 282 |
-
setTimeout(disableEnterSubmit, 100);
|
| 283 |
-
}
|
| 284 |
-
});
|
| 285 |
-
});
|
| 286 |
-
|
| 287 |
-
// Start observing
|
| 288 |
-
document.addEventListener('DOMContentLoaded', function() {
|
| 289 |
-
const body = document.querySelector('body');
|
| 290 |
-
if (body) {
|
| 291 |
-
formObserver.observe(body, { childList: true, subtree: true });
|
| 292 |
-
}
|
| 293 |
-
});
|
| 294 |
-
|
| 295 |
-
// Apply periodically as fallback
|
| 296 |
-
setInterval(disableEnterSubmit, 1000);
|
| 297 |
-
</script>
|
| 298 |
""", unsafe_allow_html=True)
|
| 299 |
|
| 300 |
# --- Initial Data Structure & Constants ---
|
|
@@ -541,39 +428,58 @@ def save_to_local_file(new_content_json_string):
|
|
| 541 |
st.error(f"Erreur de sauvegarde du fichier local: {e}")
|
| 542 |
return False
|
| 543 |
|
| 544 |
-
|
|
|
|
| 545 |
if 'editable_prompts' in st.session_state:
|
| 546 |
data_to_save = _preprocess_for_saving(st.session_state.editable_prompts)
|
| 547 |
try:
|
| 548 |
json_string = json.dumps(data_to_save, indent=4, ensure_ascii=False)
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 553 |
except Exception as e: # pragma: no cover
|
| 554 |
-
st.error(f"Erreur préparation données pour sauvegarde
|
| 555 |
-
|
| 556 |
-
def
|
| 557 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
if raw_content:
|
| 559 |
try:
|
| 560 |
loaded_data = json.loads(raw_content)
|
| 561 |
if not loaded_data or not isinstance(loaded_data, dict):
|
| 562 |
-
raise ValueError("Contenu fichier
|
| 563 |
return _postprocess_after_loading(loaded_data)
|
| 564 |
except (json.JSONDecodeError, TypeError, ValueError) as e:
|
| 565 |
-
st.info(f"Erreur chargement fichier
|
| 566 |
else:
|
| 567 |
-
st.info("Fichier
|
| 568 |
initial_data = copy.deepcopy(INITIAL_PROMPT_TEMPLATES)
|
| 569 |
if raw_content is None or raw_content == "{}":
|
| 570 |
data_to_save_init = _preprocess_for_saving(initial_data)
|
| 571 |
try:
|
| 572 |
json_string_init = json.dumps(data_to_save_init, indent=4, ensure_ascii=False)
|
| 573 |
-
|
| 574 |
-
|
|
|
|
|
|
|
| 575 |
except Exception as e: # pragma: no cover
|
| 576 |
-
st.error(f"Erreur sauvegarde initiale
|
| 577 |
return initial_data
|
| 578 |
|
| 579 |
# --- ACCESS CODE VERIFICATION ---
|
|
@@ -636,7 +542,7 @@ if not st.session_state.access_granted:
|
|
| 636 |
st.markdown("""
|
| 637 |
<p style="font-style: italic; color: #666; font-size: 0.9rem; margin-top: 1.5rem; text-align: center;">
|
| 638 |
Pour demander un accès, contactez votre référent métier ou l'administrateur de l'application :
|
| 639 |
-
<a href="mailto:arthur.causse
|
| 640 |
</p>
|
| 641 |
""", unsafe_allow_html=True)
|
| 642 |
|
|
@@ -645,7 +551,7 @@ if not st.session_state.access_granted:
|
|
| 645 |
|
| 646 |
# --- Session State Initialization ---
|
| 647 |
if 'editable_prompts' not in st.session_state:
|
| 648 |
-
st.session_state.editable_prompts =
|
| 649 |
if 'view_mode' not in st.session_state:
|
| 650 |
st.session_state.view_mode = "accueil" # Nouvelle vue par défaut
|
| 651 |
|
|
@@ -695,12 +601,12 @@ if 'assistant_existing_prompt_value' not in st.session_state:
|
|
| 695 |
|
| 696 |
# --- Sidebar Navigation with Tabs ---
|
| 697 |
st.sidebar.header("Menu Principal")
|
| 698 |
-
tab_bibliotheque, tab_injection = st.sidebar.tabs([
|
| 699 |
"📚 Bibliothèque",
|
|
|
|
| 700 |
"💡 Assistant"
|
| 701 |
])
|
| 702 |
|
| 703 |
-
|
| 704 |
# --- Tab: Bibliothèque (Sidebar content) ---
|
| 705 |
with tab_bibliotheque:
|
| 706 |
st.subheader("Explorer la Bibliothèque de Prompts")
|
|
@@ -769,6 +675,233 @@ with tab_bibliotheque:
|
|
| 769 |
st.rerun()
|
| 770 |
st.markdown("---")
|
| 771 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
# --- Tab: Injection (Sidebar content) ---
|
| 773 |
with tab_injection:
|
| 774 |
st.subheader("Assistant & Injection")
|
|
@@ -800,8 +933,6 @@ if st.session_state.get('force_select_use_case_name'):
|
|
| 800 |
st.session_state.use_case_selector_edition = st.session_state.force_select_use_case_name
|
| 801 |
st.session_state.force_select_use_case_name = None
|
| 802 |
|
| 803 |
-
final_selected_family_edition = st.session_state.get('family_selector_edition')
|
| 804 |
-
final_selected_use_case_edition = st.session_state.get('use_case_selector_edition')
|
| 805 |
library_family_to_display = st.session_state.get('library_selected_family_for_display')
|
| 806 |
|
| 807 |
# NOUVELLE SECTION POUR LA PAGE D'ACCUEIL
|
|
@@ -1052,7 +1183,7 @@ elif st.session_state.view_mode == "library":
|
|
| 1052 |
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["created_at"] = now_iso_dup_create
|
| 1053 |
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["updated_at"] = now_iso_dup_update
|
| 1054 |
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["usage_count"] = 0
|
| 1055 |
-
|
| 1056 |
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}'.")
|
| 1057 |
|
| 1058 |
st.session_state.duplicating_use_case_details = None
|
|
@@ -1079,7 +1210,7 @@ elif st.session_state.view_mode == "library":
|
|
| 1079 |
deleted_uc_name_for_msg = details['use_case']
|
| 1080 |
deleted_uc_fam_for_msg = details['family']
|
| 1081 |
del st.session_state.editable_prompts[details["family"]][details["use_case"]]
|
| 1082 |
-
|
| 1083 |
st.success(f"'{deleted_uc_name_for_msg}' supprimé de '{deleted_uc_fam_for_msg}'.")
|
| 1084 |
st.session_state.confirming_delete_details = None
|
| 1085 |
st.rerun()
|
|
@@ -1127,165 +1258,6 @@ elif st.session_state.view_mode == "library":
|
|
| 1127 |
available_families_check = list(st.session_state.editable_prompts.keys())
|
| 1128 |
if not available_families_check : st.warning("La bibliothèque est entièrement vide. Veuillez créer des métiers et des prompts.")
|
| 1129 |
|
| 1130 |
-
elif st.session_state.view_mode == "edit":
|
| 1131 |
-
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
|
| 1132 |
-
if st.button(f"⬅️ Retour à la bibliothèque ({current_family_of_edited_prompt or 'Métier'})", key="back_to_library_from_edit"):
|
| 1133 |
-
if current_family_of_edited_prompt:
|
| 1134 |
-
st.session_state.library_selected_family_for_display = current_family_of_edited_prompt
|
| 1135 |
-
st.session_state.view_mode = "library"
|
| 1136 |
-
st.rerun()
|
| 1137 |
-
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.")
|
| 1138 |
-
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.")
|
| 1139 |
-
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]:
|
| 1140 |
-
current_prompt_config = st.session_state.editable_prompts[final_selected_family_edition][final_selected_use_case_edition]
|
| 1141 |
-
st.header(f"Cas d'usage: {final_selected_use_case_edition}")
|
| 1142 |
-
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])
|
| 1143 |
-
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')}")
|
| 1144 |
-
# Afficher la description si elle existe
|
| 1145 |
-
description = current_prompt_config.get("description", "").strip()
|
| 1146 |
-
if description:
|
| 1147 |
-
st.markdown(f"*{description}*")
|
| 1148 |
-
gen_form_values = {}
|
| 1149 |
-
with st.form(key=f"gen_form_{final_selected_family_edition}_{final_selected_use_case_edition}"):
|
| 1150 |
-
st.markdown("**Remplissez le formulaire ci-dessous pour ajouter du contexte à votre prompt :**")
|
| 1151 |
-
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.")
|
| 1152 |
-
variables_for_form = current_prompt_config.get("variables", [])
|
| 1153 |
-
if not isinstance(variables_for_form, list): variables_for_form = []
|
| 1154 |
-
cols_per_row = 2 if len(variables_for_form) > 1 else 1
|
| 1155 |
-
var_chunks = [variables_for_form[i:i + cols_per_row] for i in range(0, len(variables_for_form), cols_per_row)]
|
| 1156 |
-
for chunk in var_chunks:
|
| 1157 |
-
cols = st.columns(len(chunk))
|
| 1158 |
-
for i, var_info in enumerate(chunk):
|
| 1159 |
-
with cols[i]:
|
| 1160 |
-
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")
|
| 1161 |
-
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)
|
| 1162 |
-
elif var_type == "selectbox":
|
| 1163 |
-
opts = var_info.get("options", []); idx = 0
|
| 1164 |
-
if opts:
|
| 1165 |
-
try: idx = opts.index(field_default) if field_default in opts else 0
|
| 1166 |
-
except ValueError: idx = 0 # pragma: no cover
|
| 1167 |
-
gen_form_values[var_info["name"]] = st.selectbox(var_info["label"], options=opts, index=idx, key=widget_key)
|
| 1168 |
-
elif var_type == "date_input":
|
| 1169 |
-
val_date = field_default if isinstance(field_default, date) else datetime.now().date()
|
| 1170 |
-
gen_form_values[var_info["name"]] = st.date_input(var_info["label"], value=val_date, key=widget_key)
|
| 1171 |
-
elif var_type == "number_input":
|
| 1172 |
-
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")
|
| 1173 |
-
val_num_gen = float(current_value_default_gen) if isinstance(current_value_default_gen, (int, float)) else 0.0
|
| 1174 |
-
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
|
| 1175 |
-
if min_val_gen is not None and val_num_gen < min_val_gen: val_num_gen = min_val_gen
|
| 1176 |
-
if max_val_gen is not None and val_num_gen > max_val_gen: val_num_gen = max_val_gen
|
| 1177 |
-
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")
|
| 1178 |
-
elif var_type == "text_area":
|
| 1179 |
-
height_val = var_info.get("height")
|
| 1180 |
-
final_height = None
|
| 1181 |
-
if height_val is not None:
|
| 1182 |
-
try:
|
| 1183 |
-
h = int(height_val)
|
| 1184 |
-
if h >= 68: final_height = h
|
| 1185 |
-
else: final_height = 68
|
| 1186 |
-
except (ValueError, TypeError): final_height = None
|
| 1187 |
-
else: final_height = None
|
| 1188 |
-
gen_form_values[var_info["name"]] = st.text_area(var_info["label"], value=str(field_default or ""), height=final_height, key=widget_key)
|
| 1189 |
-
if st.form_submit_button("🚀 Générer Prompt"):
|
| 1190 |
-
processed_values_for_template = {}
|
| 1191 |
-
for k, v_val in gen_form_values.items(): # gen_form_values vient de votre formulaire
|
| 1192 |
-
if v_val is None:
|
| 1193 |
-
# On ne met pas les valeurs None dans le dictionnaire,
|
| 1194 |
-
# donc les placeholders correspondants ne seront pas remplacés (comportement original)
|
| 1195 |
-
continue
|
| 1196 |
-
|
| 1197 |
-
if isinstance(v_val, date):
|
| 1198 |
-
processed_values_for_template[k] = v_val.strftime("%d/%m/%Y")
|
| 1199 |
-
elif isinstance(v_val, float): # On regroupe ici tous les traitements pour les floats
|
| 1200 |
-
if v_val.is_integer(): # Si le float est un entier (ex: 50.0)
|
| 1201 |
-
processed_values_for_template[k] = str(int(v_val)) # Convertir en "50"
|
| 1202 |
-
else: # S'il s'agit d'un float avec des décimales (ex: 0.125000...)
|
| 1203 |
-
processed_values_for_template[k] = f"{v_val:.2f}" # Formater avec 2 décimales
|
| 1204 |
-
else: # Pour tous les autres types (str, bool, etc. qui ne sont ni date ni float)
|
| 1205 |
-
processed_values_for_template[k] = str(v_val)
|
| 1206 |
-
|
| 1207 |
-
final_vals_for_prompt = processed_values_for_template # final_vals_for_prompt contient maintenant des chaînes
|
| 1208 |
-
|
| 1209 |
-
try:
|
| 1210 |
-
prompt_template_content = current_prompt_config.get("template", "")
|
| 1211 |
-
processed_template = prompt_template_content
|
| 1212 |
-
|
| 1213 |
-
# 1. Remplacer les variables connues par Streamlit (celles du formulaire)
|
| 1214 |
-
# Trier par longueur de clé (descendant) pour éviter les substitutions partielles
|
| 1215 |
-
# (ex: remplacer {jour_semaine} avant {jour})
|
| 1216 |
-
sorted_vars_for_formatting = sorted(final_vals_for_prompt.items(), key=lambda item: len(item[0]), reverse=True)
|
| 1217 |
-
|
| 1218 |
-
for var_name, var_value in sorted_vars_for_formatting:
|
| 1219 |
-
placeholder_streamlit = f"{{{var_name}}}"
|
| 1220 |
-
# Remplacer uniquement les placeholders exacts et simples
|
| 1221 |
-
processed_template = processed_template.replace(placeholder_streamlit, str(var_value))
|
| 1222 |
-
|
| 1223 |
-
# 2. Convertir les doubles accolades (pour le LLM final) en simples accolades
|
| 1224 |
-
# Ceci suppose que le template original (venant du JSON) utilisait bien {{...}}
|
| 1225 |
-
# pour les placeholders destinés au LLM final.
|
| 1226 |
-
formatted_template_content = processed_template.replace("{{", "{").replace("}}", "}")
|
| 1227 |
-
|
| 1228 |
-
use_case_title = final_selected_use_case_edition
|
| 1229 |
-
generated_prompt = f"Sujet : {use_case_title}\n{formatted_template_content}"
|
| 1230 |
-
st.session_state.active_generated_prompt = generated_prompt
|
| 1231 |
-
st.success("Prompt généré avec succès!")
|
| 1232 |
-
st.balloons()
|
| 1233 |
-
current_prompt_config["usage_count"] = current_prompt_config.get("usage_count", 0) + 1
|
| 1234 |
-
current_prompt_config["updated_at"] = datetime.now().isoformat()
|
| 1235 |
-
save_editable_prompts_to_local()
|
| 1236 |
-
|
| 1237 |
-
except Exception as e: # Garder un catch-all pour les erreurs imprévues
|
| 1238 |
-
st.error(f"Erreur inattendue lors de la génération du prompt : {e}") # pragma: no cover
|
| 1239 |
-
st.session_state.active_generated_prompt = f"ERREUR INATTENDUE - TEMPLATE ORIGINAL :\n---\n{prompt_template_content}" # pragma: no cover
|
| 1240 |
-
st.markdown("---")
|
| 1241 |
-
if st.session_state.active_generated_prompt:
|
| 1242 |
-
st.subheader("✅ Prompt Généré (éditable):")
|
| 1243 |
-
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")
|
| 1244 |
-
if edited_prompt_value != st.session_state.active_generated_prompt:
|
| 1245 |
-
st.session_state.active_generated_prompt = edited_prompt_value # pragma: no cover
|
| 1246 |
-
col_caption, col_indicator = st.columns([1.8, 0.2]) # Ajustez les proportions si nécessaire
|
| 1247 |
-
with col_caption:
|
| 1248 |
-
st.caption("Prompt généré (pour relecture et copie manuelle) :")
|
| 1249 |
-
with col_indicator:
|
| 1250 |
-
st.markdown("<div style='color:red; text-align:right; font-size:0.9em; padding-right:0.9em;'>Copier ici : 👇</div>", unsafe_allow_html=True)
|
| 1251 |
-
|
| 1252 |
-
|
| 1253 |
-
if st.session_state.active_generated_prompt:
|
| 1254 |
-
st.code(st.session_state.active_generated_prompt, language='markdown', line_numbers=True)
|
| 1255 |
-
else:
|
| 1256 |
-
st.markdown("*Aucun prompt généré à afficher.*")
|
| 1257 |
-
|
| 1258 |
-
st.markdown("---") # Un petit séparateur
|
| 1259 |
-
|
| 1260 |
-
prompt_text_escaped_for_js = json.dumps(st.session_state.active_generated_prompt)
|
| 1261 |
-
|
| 1262 |
-
should_expand_config = SHOW_CONFIG_SECTION and st.session_state.get('go_to_config_section', False)
|
| 1263 |
-
if SHOW_CONFIG_SECTION:
|
| 1264 |
-
with st.expander(f"⚙️ Paramétrage du Prompt: {final_selected_use_case_edition}", expanded=should_expand_config):
|
| 1265 |
-
st.info("Section de paramétrage temporairement désactivée.")
|
| 1266 |
-
|
| 1267 |
-
if st.session_state.get('go_to_config_section'):
|
| 1268 |
-
st.session_state.go_to_config_section = False
|
| 1269 |
-
|
| 1270 |
-
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:
|
| 1271 |
-
details = st.session_state.confirming_delete_details; st.warning(f"Supprimer '{details['use_case']}' de '{details['family']}' ? Action irréversible.")
|
| 1272 |
-
c1_del_uc, c2_del_uc, _ = st.columns([1,1,3])
|
| 1273 |
-
if c1_del_uc.button(f"Oui, supprimer '{details['use_case']}'", key=f"del_yes_{details['family']}_{details['use_case']}", type="primary"):
|
| 1274 |
-
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}'.")
|
| 1275 |
-
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
|
| 1276 |
-
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
|
| 1277 |
-
st.session_state.active_generated_prompt = ""; st.session_state.variable_type_to_create = None; st.session_state.view_mode = "edit"; st.rerun()
|
| 1278 |
-
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()
|
| 1279 |
-
st.markdown("---")
|
| 1280 |
-
|
| 1281 |
-
else:
|
| 1282 |
-
if not final_selected_family_edition:
|
| 1283 |
-
st.info("Veuillez sélectionner un métier dans la barre latérale (onglet Édition) pour commencer.")
|
| 1284 |
-
elif not final_selected_use_case_edition:
|
| 1285 |
-
st.info(f"Veuillez sélectionner un cas d'usage pour le métier '{final_selected_family_edition}' ou en créer un.")
|
| 1286 |
-
else:
|
| 1287 |
-
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.")
|
| 1288 |
-
st.session_state.use_case_selector_edition = None # pragma: no cover
|
| 1289 |
|
| 1290 |
elif st.session_state.view_mode == "generator":
|
| 1291 |
generator_family = st.session_state.get('generator_selected_family')
|
|
@@ -1442,7 +1414,7 @@ elif st.session_state.view_mode == "generator":
|
|
| 1442 |
st.balloons()
|
| 1443 |
current_prompt_config["usage_count"] = current_prompt_config.get("usage_count", 0) + 1
|
| 1444 |
current_prompt_config["updated_at"] = datetime.now().isoformat()
|
| 1445 |
-
|
| 1446 |
|
| 1447 |
except Exception as e:
|
| 1448 |
st.error(f"Erreur inattendue lors de la génération du prompt : {e}")
|
|
@@ -1476,7 +1448,253 @@ elif st.session_state.view_mode == "generator":
|
|
| 1476 |
should_expand_config = SHOW_CONFIG_SECTION and st.session_state.get('go_to_config_section', False)
|
| 1477 |
if SHOW_CONFIG_SECTION:
|
| 1478 |
with st.expander(f"⚙️ Paramétrage du Prompt: {generator_use_case}", expanded=should_expand_config):
|
| 1479 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1480 |
|
| 1481 |
if st.session_state.get('go_to_config_section'):
|
| 1482 |
st.session_state.go_to_config_section = False
|
|
@@ -1552,11 +1770,11 @@ elif st.session_state.view_mode == "inject_manual":
|
|
| 1552 |
if first_new_uc_name is None:
|
| 1553 |
first_new_uc_name = uc_name_stripped
|
| 1554 |
if successful_injections:
|
| 1555 |
-
|
| 1556 |
st.success(f"{len(successful_injections)} cas d'usage injectés avec succès dans '{target_family_name}': {', '.join(successful_injections)}")
|
| 1557 |
st.session_state.injection_json_text = ""
|
| 1558 |
if first_new_uc_name:
|
| 1559 |
-
st.session_state.view_mode = "
|
| 1560 |
st.session_state.force_select_family_name = target_family_name
|
| 1561 |
st.session_state.force_select_use_case_name = first_new_uc_name
|
| 1562 |
st.session_state.go_to_config_section = True
|
|
@@ -1610,7 +1828,7 @@ elif st.session_state.view_mode == "assistant_creation": # Cette vue gère maint
|
|
| 1610 |
st.rerun()
|
| 1611 |
|
| 1612 |
if st.session_state.assistant_mode == "creation":
|
| 1613 |
-
st.markdown("Décrivez votre besoin pour que l'assistant génère une instruction détaillée. Vous donnerez cette instruction à
|
| 1614 |
with st.form(key="assistant_creation_form_std"):
|
| 1615 |
# Initialiser current_form_input_values avec les valeurs de session_state ou les valeurs par défaut
|
| 1616 |
# pour que les champs du formulaire soient pré-remplis correctement.
|
|
@@ -1659,7 +1877,7 @@ elif st.session_state.view_mode == "assistant_creation": # Cette vue gère maint
|
|
| 1659 |
st.session_state.generated_meta_prompt_for_llm = ""
|
| 1660 |
|
| 1661 |
elif st.session_state.assistant_mode == "amelioration":
|
| 1662 |
-
st.markdown("Collez votre prompt existant. L'assistant générera une instruction pour
|
| 1663 |
with st.form(key="assistant_amelioration_form_unified"):
|
| 1664 |
# Utilise la valeur de session_state pour ce champ
|
| 1665 |
prompt_existant_input_val = st.text_area(
|
|
@@ -1693,14 +1911,14 @@ elif st.session_state.view_mode == "assistant_creation": # Cette vue gère maint
|
|
| 1693 |
if st.session_state.generated_meta_prompt_for_llm:
|
| 1694 |
col_subheader_assist, col_indicator_assist = st.columns([0.85, 0.15])
|
| 1695 |
with col_subheader_assist:
|
| 1696 |
-
st.subheader("📋 Instruction Générée (à coller dans
|
| 1697 |
with col_indicator_assist:
|
| 1698 |
st.markdown("<div style='color:red; text-align:right; font-size:0.9em; padding-top:1.9em;padding-right:0.9em;'>Copier ici : 👇</div>", unsafe_allow_html=True)
|
| 1699 |
|
| 1700 |
st.code(st.session_state.generated_meta_prompt_for_llm, language='markdown', line_numbers=True)
|
| 1701 |
st.caption("<span style='color:gray; font-size:0.9em;'>Utilisez l'icône en haut à droite du bloc de code pour copier l'instruction.</span>", unsafe_allow_html=True)
|
| 1702 |
st.markdown("---")
|
| 1703 |
-
st.info("Une fois que
|
| 1704 |
if st.button("💉 Injecter JSON Manuellement", key="prepare_inject_from_assistant_unified_btn", use_container_width=True, type="primary"):
|
| 1705 |
st.session_state.view_mode = "inject_manual"
|
| 1706 |
st.session_state.injection_selected_family = None
|
|
@@ -1709,10 +1927,10 @@ elif st.session_state.view_mode == "assistant_creation": # Cette vue gère maint
|
|
| 1709 |
st.rerun()
|
| 1710 |
if not any(st.session_state.editable_prompts.values()): # pragma: no cover
|
| 1711 |
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.")
|
| 1712 |
-
elif st.session_state.view_mode not in ["library", "
|
| 1713 |
-
st.session_state.view_mode = "library" if list(st.session_state.editable_prompts.keys()) else "
|
| 1714 |
st.rerun()
|
| 1715 |
|
| 1716 |
# --- Sidebar Footer ---
|
| 1717 |
st.sidebar.markdown("---")
|
| 1718 |
-
st.sidebar.info(f"Générateur v3.3.6 - © {CURRENT_YEAR}
|
|
|
|
| 4 |
import json
|
| 5 |
import requests
|
| 6 |
import os
|
| 7 |
+
# --- Hugging Face Hub integration ---
|
| 8 |
+
try:
|
| 9 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 10 |
+
HF_HUB_AVAILABLE = True
|
| 11 |
+
except ImportError:
|
| 12 |
+
HF_HUB_AVAILABLE = False
|
| 13 |
+
|
| 14 |
+
HF_REPO_ID = "AIB-Research/bibliPrompt"
|
| 15 |
+
HF_JSON_FILENAME = "prompt_templates_data_v3.json"
|
| 16 |
+
HF_TOKEN = os.environ.get("HF_TOKEN", None)
|
| 17 |
+
|
| 18 |
+
# --- Save to Hugging Face Space ---
|
| 19 |
+
def save_to_hf_space(json_path, repo_id=HF_REPO_ID, token=HF_TOKEN):
|
| 20 |
+
if not HF_HUB_AVAILABLE:
|
| 21 |
+
st.warning("huggingface_hub n'est pas installé. Sauvegarde sur HF désactivée.")
|
| 22 |
+
return False
|
| 23 |
+
try:
|
| 24 |
+
api = HfApi()
|
| 25 |
+
api.upload_file(
|
| 26 |
+
path_or_fileobj=json_path,
|
| 27 |
+
path_in_repo=HF_JSON_FILENAME,
|
| 28 |
+
repo_id=repo_id,
|
| 29 |
+
repo_type="space",
|
| 30 |
+
token=token
|
| 31 |
+
)
|
| 32 |
+
st.toast("💾 Données sauvegardées sur Hugging Face Space!", icon="🤗")
|
| 33 |
+
return True
|
| 34 |
+
except Exception as e:
|
| 35 |
+
st.warning(f"Erreur lors de la sauvegarde sur Hugging Face: {e}")
|
| 36 |
+
return False
|
| 37 |
+
|
| 38 |
+
# --- Load from Hugging Face Space ---
|
| 39 |
+
def load_from_hf_space(repo_id=HF_REPO_ID, token=HF_TOKEN):
|
| 40 |
+
if not HF_HUB_AVAILABLE:
|
| 41 |
+
st.warning("huggingface_hub n'est pas installé. Chargement depuis HF désactivé.")
|
| 42 |
+
return None
|
| 43 |
+
try:
|
| 44 |
+
local_path = hf_hub_download(
|
| 45 |
+
repo_id=repo_id,
|
| 46 |
+
filename=HF_JSON_FILENAME,
|
| 47 |
+
repo_type="space",
|
| 48 |
+
token=token
|
| 49 |
+
)
|
| 50 |
+
with open(local_path, "r", encoding="utf-8") as f:
|
| 51 |
+
return f.read()
|
| 52 |
+
except Exception as e:
|
| 53 |
+
st.warning(f"Erreur lors du chargement depuis Hugging Face: {e}")
|
| 54 |
+
return None
|
| 55 |
|
| 56 |
# --- PAGE CONFIGURATION (MUST BE THE FIRST STREAMLIT COMMAND) ---
|
| 57 |
# Fixed deployment issue
|
|
|
|
| 62 |
<style>
|
| 63 |
/* Cible le bouton spécifique que vous avez identifié */
|
| 64 |
button[data-testid="stBaseButton-headerNoPadding"]::after {
|
| 65 |
+
content: " Menu";
|
| 66 |
+
margin-left: 8px;
|
| 67 |
+
font-size: 0.9em;
|
| 68 |
+
vertical-align: middle;
|
| 69 |
+
color: inherit;
|
| 70 |
+
font-weight: normal;
|
| 71 |
+
display: inline-flex;
|
| 72 |
align-items: center;
|
| 73 |
}
|
| 74 |
div[data-testid="stCodeBlock"] pre,
|
| 75 |
+
pre.st-emotion-cache-1nqbjoj {
|
|
|
|
|
|
|
| 76 |
max-height: 520px !important;
|
| 77 |
overflow-y: auto !important;
|
| 78 |
font-size: 0.875em !important;
|
| 79 |
+
display: block !important;
|
|
|
|
| 80 |
visibility: visible !important;
|
| 81 |
opacity: 1 !important;
|
| 82 |
}
|
|
|
|
|
|
|
| 83 |
div[data-testid="stCodeBlock"] > div:first-child {
|
| 84 |
+
max-height: 520px !important;
|
|
|
|
| 85 |
overflow-y: auto !important;
|
| 86 |
+
display: block !important;
|
| 87 |
visibility: visible !important;
|
| 88 |
opacity: 1 !important;
|
| 89 |
}
|
|
|
|
|
|
|
| 90 |
pre.st-emotion-cache-1nqbjoj > div[style*="background-color: transparent;"] {
|
| 91 |
+
height: auto !important;
|
| 92 |
+
max-height: 100% !important;
|
| 93 |
overflow-y: auto !important;
|
| 94 |
}
|
|
|
|
| 95 |
button[data-testid="stCodeCopyButton"] {
|
| 96 |
opacity: 0.85 !important;
|
| 97 |
visibility: visible !important;
|
|
|
|
| 100 |
border-radius: 4px !important;
|
| 101 |
padding: 3px 5px !important;
|
| 102 |
transition: opacity 0.15s ease-in-out, background-color 0.15s ease-in-out;
|
|
|
|
|
|
|
| 103 |
}
|
|
|
|
| 104 |
button[data-testid="stCodeCopyButton"]:hover {
|
| 105 |
opacity: 1 !important;
|
| 106 |
background-color: #e6e8eb !important;
|
| 107 |
border-color: #b0b0b0 !important;
|
| 108 |
}
|
|
|
|
| 109 |
button[data-testid="stCodeCopyButton"] svg {
|
| 110 |
+
transform: scale(1.2);
|
| 111 |
vertical-align: middle;
|
| 112 |
}
|
|
|
|
|
|
|
|
|
|
| 113 |
section[data-testid="stSidebar"] {
|
| 114 |
width: 31.5rem !important;
|
| 115 |
min-width: 31.5rem !important;
|
| 116 |
max-width: 31.5rem !important;
|
| 117 |
}
|
|
|
|
|
|
|
| 118 |
section[data-testid="stSidebar"][aria-expanded="false"] {
|
| 119 |
width: 0rem !important;
|
| 120 |
min-width: 0rem !important;
|
| 121 |
max-width: 0rem !important;
|
| 122 |
overflow: hidden !important;
|
| 123 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
[data-testid="stMainBlockContainer"] {
|
| 125 |
padding-top: 3rem !important;
|
| 126 |
}
|
|
|
|
| 127 |
.css-1d391kg, .css-18e3th9 {
|
| 128 |
padding-top: 3rem !important;
|
| 129 |
}
|
|
|
|
|
|
|
| 130 |
h1[data-testid="stHeading"]:first-of-type {
|
| 131 |
margin-top: -2rem !important;
|
| 132 |
padding-top: 0rem !important;
|
| 133 |
}
|
|
|
|
| 134 |
h1:contains("Bienvenue dans votre laboratoire") {
|
| 135 |
margin-top: -2rem !important;
|
| 136 |
padding-top: 0rem !important;
|
| 137 |
}
|
|
|
|
|
|
|
| 138 |
@media (max-width: 768px) {
|
| 139 |
.main .block-container {
|
| 140 |
max-width: 100vw !important;
|
| 141 |
width: 100vw !important;
|
| 142 |
}
|
| 143 |
}
|
| 144 |
+
div[data-testid="stExpander"] div[data-testid="stCodeBlock"] {
|
| 145 |
+
margin-top: 0.1rem !important;
|
| 146 |
+
margin-bottom: 0.15rem !important;
|
| 147 |
+
padding-top: 0.1rem !important;
|
| 148 |
+
padding-bottom: 0.1rem !important;
|
| 149 |
+
}
|
| 150 |
+
div[data-testid="stExpander"] div[data-testid="stCodeBlock"] pre {
|
| 151 |
+
padding-top: 0.2rem !important;
|
| 152 |
+
padding-bottom: 0.2rem !important;
|
| 153 |
+
line-height: 1.1 !important;
|
| 154 |
+
font-size: 0.85em !important;
|
| 155 |
+
margin: 0 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
}
|
| 157 |
+
div[data-testid="stExpander"] {
|
| 158 |
+
width: 100% !important;
|
|
|
|
|
|
|
|
|
|
| 159 |
max-width: 100% !important;
|
| 160 |
+
overflow: visible !important;
|
| 161 |
+
}
|
| 162 |
+
div[data-testid="stExpander"] > div:first-child {
|
| 163 |
+
overflow: visible !important;
|
| 164 |
+
}
|
| 165 |
+
div[data-testid="stExpander"] .streamlit-expanderContent {
|
| 166 |
width: 100% !important;
|
| 167 |
+
max-width: 100% !important;
|
| 168 |
+
overflow: visible !important;
|
| 169 |
+
position: relative !important;
|
| 170 |
+
transform: translateZ(0) !important;
|
| 171 |
}
|
| 172 |
+
.main .block-container {
|
| 173 |
+
overflow-x: hidden !important;
|
| 174 |
+
overflow-y: auto !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
}
|
| 176 |
+
div[data-testid="stExpander"] form {
|
| 177 |
+
max-width: 100% !important;
|
| 178 |
+
overflow-x: hidden !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
}
|
| 180 |
+
div[data-testid="stExpander"] div[data-testid="column"] {
|
| 181 |
+
min-width: 0 !important;
|
| 182 |
+
overflow: hidden !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
}
|
| 184 |
+
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
""", unsafe_allow_html=True)
|
| 186 |
|
| 187 |
# --- Initial Data Structure & Constants ---
|
|
|
|
| 428 |
st.error(f"Erreur de sauvegarde du fichier local: {e}")
|
| 429 |
return False
|
| 430 |
|
| 431 |
+
# --- Unified save/load functions ---
|
| 432 |
+
def save_editable_prompts_to_local_and_hf():
|
| 433 |
if 'editable_prompts' in st.session_state:
|
| 434 |
data_to_save = _preprocess_for_saving(st.session_state.editable_prompts)
|
| 435 |
try:
|
| 436 |
json_string = json.dumps(data_to_save, indent=4, ensure_ascii=False)
|
| 437 |
+
local_ok = save_to_local_file(json_string)
|
| 438 |
+
hf_ok = False
|
| 439 |
+
# Save to HF Space if possible
|
| 440 |
+
with open(LOCAL_DATA_FILENAME, 'w', encoding='utf-8') as f:
|
| 441 |
+
f.write(json_string)
|
| 442 |
+
if HF_HUB_AVAILABLE:
|
| 443 |
+
hf_ok = save_to_hf_space(LOCAL_DATA_FILENAME)
|
| 444 |
+
if local_ok and hf_ok:
|
| 445 |
+
st.toast("💾 Données sauvegardées localement et sur Hugging Face!", icon="💾")
|
| 446 |
+
elif local_ok:
|
| 447 |
+
st.toast("💾 Données sauvegardées localement!", icon="💾")
|
| 448 |
+
elif hf_ok:
|
| 449 |
+
st.toast("💾 Données sauvegardées sur Hugging Face!", icon="🤗")
|
| 450 |
+
else:
|
| 451 |
+
st.warning("Sauvegarde échouée.")
|
| 452 |
except Exception as e: # pragma: no cover
|
| 453 |
+
st.error(f"Erreur préparation données pour sauvegarde: {e}")
|
| 454 |
+
|
| 455 |
+
def load_editable_prompts_from_local_or_hf():
|
| 456 |
+
# Try HF first, fallback to local
|
| 457 |
+
raw_content = None
|
| 458 |
+
if HF_HUB_AVAILABLE:
|
| 459 |
+
raw_content = load_from_hf_space()
|
| 460 |
+
if not raw_content:
|
| 461 |
+
raw_content = get_local_file_content()
|
| 462 |
if raw_content:
|
| 463 |
try:
|
| 464 |
loaded_data = json.loads(raw_content)
|
| 465 |
if not loaded_data or not isinstance(loaded_data, dict):
|
| 466 |
+
raise ValueError("Contenu fichier vide ou mal structuré.")
|
| 467 |
return _postprocess_after_loading(loaded_data)
|
| 468 |
except (json.JSONDecodeError, TypeError, ValueError) as e:
|
| 469 |
+
st.info(f"Erreur chargement fichier ('{str(e)[:50]}...'). Initialisation avec modèles par défaut.")
|
| 470 |
else:
|
| 471 |
+
st.info("Fichier vide ou inaccessible. Initialisation avec modèles par défaut.")
|
| 472 |
initial_data = copy.deepcopy(INITIAL_PROMPT_TEMPLATES)
|
| 473 |
if raw_content is None or raw_content == "{}":
|
| 474 |
data_to_save_init = _preprocess_for_saving(initial_data)
|
| 475 |
try:
|
| 476 |
json_string_init = json.dumps(data_to_save_init, indent=4, ensure_ascii=False)
|
| 477 |
+
save_to_local_file(json_string_init)
|
| 478 |
+
if HF_HUB_AVAILABLE:
|
| 479 |
+
save_to_hf_space(LOCAL_DATA_FILENAME)
|
| 480 |
+
st.info("Modèles par défaut sauvegardés pour initialisation.")
|
| 481 |
except Exception as e: # pragma: no cover
|
| 482 |
+
st.error(f"Erreur sauvegarde initiale: {e}")
|
| 483 |
return initial_data
|
| 484 |
|
| 485 |
# --- ACCESS CODE VERIFICATION ---
|
|
|
|
| 542 |
st.markdown("""
|
| 543 |
<p style="font-style: italic; color: #666; font-size: 0.9rem; margin-top: 1.5rem; text-align: center;">
|
| 544 |
Pour demander un accès, contactez votre référent métier ou l'administrateur de l'application :
|
| 545 |
+
<a href="mailto:arthur.causse@aibuilders.ai" style="color: #0066cc;">arthur.causse@aibuilders.ai</a>
|
| 546 |
</p>
|
| 547 |
""", unsafe_allow_html=True)
|
| 548 |
|
|
|
|
| 551 |
|
| 552 |
# --- Session State Initialization ---
|
| 553 |
if 'editable_prompts' not in st.session_state:
|
| 554 |
+
st.session_state.editable_prompts = load_editable_prompts_from_local_or_hf()
|
| 555 |
if 'view_mode' not in st.session_state:
|
| 556 |
st.session_state.view_mode = "accueil" # Nouvelle vue par défaut
|
| 557 |
|
|
|
|
| 601 |
|
| 602 |
# --- Sidebar Navigation with Tabs ---
|
| 603 |
st.sidebar.header("Menu Principal")
|
| 604 |
+
tab_bibliotheque, tab_edition_generation, tab_injection = st.sidebar.tabs([
|
| 605 |
"📚 Bibliothèque",
|
| 606 |
+
"✍️ Édition",
|
| 607 |
"💡 Assistant"
|
| 608 |
])
|
| 609 |
|
|
|
|
| 610 |
# --- Tab: Bibliothèque (Sidebar content) ---
|
| 611 |
with tab_bibliotheque:
|
| 612 |
st.subheader("Explorer la Bibliothèque de Prompts")
|
|
|
|
| 675 |
st.rerun()
|
| 676 |
st.markdown("---")
|
| 677 |
|
| 678 |
+
# --- Tab: Édition (Sidebar content) ---
|
| 679 |
+
with tab_edition_generation:
|
| 680 |
+
st.subheader("Explorateur de Prompts")
|
| 681 |
+
available_families = list(st.session_state.editable_prompts.keys())
|
| 682 |
+
default_family_idx_edit = 0
|
| 683 |
+
current_family_for_edit = st.session_state.get('family_selector_edition')
|
| 684 |
+
|
| 685 |
+
if st.session_state.force_select_family_name and st.session_state.force_select_family_name in available_families:
|
| 686 |
+
current_family_for_edit = st.session_state.force_select_family_name
|
| 687 |
+
st.session_state.family_selector_edition = current_family_for_edit
|
| 688 |
+
elif current_family_for_edit and current_family_for_edit in available_families:
|
| 689 |
+
pass
|
| 690 |
+
elif available_families:
|
| 691 |
+
current_family_for_edit = available_families[0]
|
| 692 |
+
st.session_state.family_selector_edition = current_family_for_edit
|
| 693 |
+
else:
|
| 694 |
+
current_family_for_edit = None
|
| 695 |
+
st.session_state.family_selector_edition = None
|
| 696 |
+
|
| 697 |
+
if current_family_for_edit and current_family_for_edit in available_families:
|
| 698 |
+
default_family_idx_edit = available_families.index(current_family_for_edit)
|
| 699 |
+
elif available_families:
|
| 700 |
+
default_family_idx_edit = 0
|
| 701 |
+
current_family_for_edit = available_families[0]
|
| 702 |
+
st.session_state.family_selector_edition = current_family_for_edit
|
| 703 |
+
else:
|
| 704 |
+
default_family_idx_edit = 0
|
| 705 |
+
|
| 706 |
+
if not available_families:
|
| 707 |
+
st.info("Aucune famille de métier de cas d'usage. Créez-en une via les options ci-dessous.")
|
| 708 |
+
else:
|
| 709 |
+
prev_family_selection_edit = st.session_state.get('family_selector_edition')
|
| 710 |
+
selected_family_ui_edit = st.selectbox(
|
| 711 |
+
"Métier :",
|
| 712 |
+
options=available_families,
|
| 713 |
+
index=default_family_idx_edit,
|
| 714 |
+
key='family_selectbox_widget_edit',
|
| 715 |
+
help="Sélectionnez une métier pour voir ses cas d'usage."
|
| 716 |
+
)
|
| 717 |
+
if st.session_state.family_selector_edition != selected_family_ui_edit :
|
| 718 |
+
st.session_state.family_selector_edition = selected_family_ui_edit
|
| 719 |
+
|
| 720 |
+
if prev_family_selection_edit != selected_family_ui_edit:
|
| 721 |
+
st.session_state.use_case_selector_edition = None
|
| 722 |
+
st.session_state.force_select_use_case_name = None
|
| 723 |
+
st.session_state.view_mode = "generator"
|
| 724 |
+
st.session_state.active_generated_prompt = ""
|
| 725 |
+
st.session_state.variable_type_to_create = None
|
| 726 |
+
st.session_state.editing_variable_info = None
|
| 727 |
+
st.rerun()
|
| 728 |
+
|
| 729 |
+
current_selected_family_for_edit_logic = st.session_state.get('family_selector_edition')
|
| 730 |
+
use_cases_in_current_family_edit_options = []
|
| 731 |
+
if current_selected_family_for_edit_logic and current_selected_family_for_edit_logic in st.session_state.editable_prompts:
|
| 732 |
+
use_cases_in_current_family_edit_options = sorted(list(st.session_state.editable_prompts[current_selected_family_for_edit_logic].keys()))
|
| 733 |
+
|
| 734 |
+
if use_cases_in_current_family_edit_options:
|
| 735 |
+
default_uc_idx_edit = 0
|
| 736 |
+
current_uc_for_edit = st.session_state.get('use_case_selector_edition')
|
| 737 |
+
|
| 738 |
+
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:
|
| 739 |
+
current_uc_for_edit = st.session_state.force_select_use_case_name
|
| 740 |
+
elif current_uc_for_edit and current_uc_for_edit in use_cases_in_current_family_edit_options:
|
| 741 |
+
pass
|
| 742 |
+
elif use_cases_in_current_family_edit_options:
|
| 743 |
+
current_uc_for_edit = use_cases_in_current_family_edit_options[0]
|
| 744 |
+
|
| 745 |
+
st.session_state.use_case_selector_edition = current_uc_for_edit
|
| 746 |
+
|
| 747 |
+
if current_uc_for_edit and current_uc_for_edit in use_cases_in_current_family_edit_options:
|
| 748 |
+
default_uc_idx_edit = use_cases_in_current_family_edit_options.index(current_uc_for_edit)
|
| 749 |
+
|
| 750 |
+
prev_uc_selection_edit = st.session_state.get('use_case_selector_edition')
|
| 751 |
+
selected_use_case_ui_edit = st.radio(
|
| 752 |
+
"Cas d'usage :",
|
| 753 |
+
options=use_cases_in_current_family_edit_options,
|
| 754 |
+
index=default_uc_idx_edit,
|
| 755 |
+
key='use_case_radio_widget_edit',
|
| 756 |
+
help="Sélectionnez un cas d'usage pour générer un prompt ou le paramétrer."
|
| 757 |
+
)
|
| 758 |
+
if st.session_state.use_case_selector_edition != selected_use_case_ui_edit:
|
| 759 |
+
st.session_state.use_case_selector_edition = selected_use_case_ui_edit
|
| 760 |
+
|
| 761 |
+
if prev_uc_selection_edit != selected_use_case_ui_edit:
|
| 762 |
+
st.session_state.view_mode = "generator"
|
| 763 |
+
st.session_state.active_generated_prompt = ""
|
| 764 |
+
st.session_state.variable_type_to_create = None
|
| 765 |
+
st.session_state.editing_variable_info = None
|
| 766 |
+
st.rerun()
|
| 767 |
+
|
| 768 |
+
elif current_selected_family_for_edit_logic:
|
| 769 |
+
st.info(f"Aucun cas d'usage dans '{current_selected_family_for_edit_logic}'. Créez-en un.")
|
| 770 |
+
st.session_state.use_case_selector_edition = None
|
| 771 |
+
|
| 772 |
+
if st.session_state.force_select_family_name: st.session_state.force_select_family_name = None
|
| 773 |
+
if st.session_state.force_select_use_case_name: st.session_state.force_select_use_case_name = None
|
| 774 |
+
st.markdown("---")
|
| 775 |
+
|
| 776 |
+
with st.expander("🗂️ Gérer les familles de prompts par métier", expanded=False):
|
| 777 |
+
with st.form("new_family_form_sidebar", clear_on_submit=True):
|
| 778 |
+
new_family_name = st.text_input("Nom du nouveau métier:", key="new_fam_name_sidebar")
|
| 779 |
+
submitted_new_family = st.form_submit_button("➕ Créer métier")
|
| 780 |
+
if submitted_new_family and new_family_name.strip():
|
| 781 |
+
if new_family_name.strip() in st.session_state.editable_prompts:
|
| 782 |
+
st.error(f"Le métier '{new_family_name.strip()}' existe déjà.")
|
| 783 |
+
else:
|
| 784 |
+
st.session_state.editable_prompts[new_family_name.strip()] = {}
|
| 785 |
+
save_editable_prompts_to_local_and_hf()
|
| 786 |
+
st.success(f"Métier '{new_family_name.strip()}' créée.")
|
| 787 |
+
st.session_state.force_select_family_name = new_family_name.strip()
|
| 788 |
+
st.session_state.use_case_selector_edition = None
|
| 789 |
+
st.session_state.view_mode = "generator"
|
| 790 |
+
st.rerun()
|
| 791 |
+
elif submitted_new_family:
|
| 792 |
+
st.error("Le nom du métier ne peut pas être vide.")
|
| 793 |
+
|
| 794 |
+
if available_families and current_selected_family_for_edit_logic :
|
| 795 |
+
st.markdown("---")
|
| 796 |
+
with st.form("rename_family_form_sidebar"):
|
| 797 |
+
st.write(f"Renommer le métier : **{current_selected_family_for_edit_logic}**")
|
| 798 |
+
renamed_family_name_input = st.text_input("Nouveau nom :", value=current_selected_family_for_edit_logic, key="ren_fam_name_sidebar")
|
| 799 |
+
submitted_rename_family = st.form_submit_button("✏️ Renommer")
|
| 800 |
+
if submitted_rename_family and renamed_family_name_input.strip():
|
| 801 |
+
renamed_family_name = renamed_family_name_input.strip()
|
| 802 |
+
if renamed_family_name == current_selected_family_for_edit_logic:
|
| 803 |
+
st.info("Le nouveau nom est identique à l'ancien.")
|
| 804 |
+
elif renamed_family_name in st.session_state.editable_prompts:
|
| 805 |
+
st.error(f"Un métier nommé '{renamed_family_name}' existe déjà.")
|
| 806 |
+
else:
|
| 807 |
+
st.session_state.editable_prompts[renamed_family_name] = st.session_state.editable_prompts.pop(current_selected_family_for_edit_logic)
|
| 808 |
+
save_editable_prompts_to_local_and_hf()
|
| 809 |
+
st.success(f"Métier '{current_selected_family_for_edit_logic}' renommé en '{renamed_family_name}'.")
|
| 810 |
+
st.session_state.force_select_family_name = renamed_family_name
|
| 811 |
+
if st.session_state.library_selected_family_for_display == current_selected_family_for_edit_logic:
|
| 812 |
+
st.session_state.library_selected_family_for_display = renamed_family_name
|
| 813 |
+
st.session_state.view_mode = "generator"
|
| 814 |
+
st.rerun()
|
| 815 |
+
elif submitted_rename_family:
|
| 816 |
+
st.error("Le nouveau nom du métier ne peut pas être vide.")
|
| 817 |
+
|
| 818 |
+
st.markdown("---")
|
| 819 |
+
st.write(f"Supprimer le métier : **{current_selected_family_for_edit_logic}**")
|
| 820 |
+
if st.session_state.confirming_delete_family_name == current_selected_family_for_edit_logic:
|
| 821 |
+
st.warning(f"Supprimer '{current_selected_family_for_edit_logic}' et tous ses cas d'usage ? Action irréversible.")
|
| 822 |
+
|
| 823 |
+
_text_confirm_delete = f"Oui, supprimer définitivement '{current_selected_family_for_edit_logic}'"
|
| 824 |
+
if st.button(_text_confirm_delete, type="primary", key=f"confirm_del_fam_sb_{current_selected_family_for_edit_logic}", use_container_width=True):
|
| 825 |
+
deleted_fam_name = current_selected_family_for_edit_logic
|
| 826 |
+
del st.session_state.editable_prompts[current_selected_family_for_edit_logic]
|
| 827 |
+
save_editable_prompts_to_local_and_hf()
|
| 828 |
+
st.success(f"Métier '{deleted_fam_name}' supprimée.")
|
| 829 |
+
st.session_state.confirming_delete_family_name = None
|
| 830 |
+
st.session_state.family_selector_edition = None
|
| 831 |
+
st.session_state.use_case_selector_edition = None
|
| 832 |
+
if st.session_state.library_selected_family_for_display == deleted_fam_name:
|
| 833 |
+
st.session_state.library_selected_family_for_display = None
|
| 834 |
+
st.session_state.view_mode = "library"
|
| 835 |
+
st.rerun()
|
| 836 |
+
|
| 837 |
+
if st.button("Non, annuler la suppression", key=f"cancel_del_fam_sb_{current_selected_family_for_edit_logic}", use_container_width=True):
|
| 838 |
+
st.session_state.confirming_delete_family_name = None
|
| 839 |
+
st.session_state.view_mode = "generator"
|
| 840 |
+
st.rerun()
|
| 841 |
+
else:
|
| 842 |
+
if st.button(f"🗑️ Supprimer le métier Sélectionnée", key=f"del_fam_btn_sb_{current_selected_family_for_edit_logic}"):
|
| 843 |
+
st.session_state.confirming_delete_family_name = current_selected_family_for_edit_logic
|
| 844 |
+
st.session_state.view_mode = "generator"
|
| 845 |
+
st.rerun()
|
| 846 |
+
elif not available_families:
|
| 847 |
+
st.caption("Créez un métier pour pouvoir le gérer.")
|
| 848 |
+
else:
|
| 849 |
+
st.caption("Sélectionnez un métier (ci-dessus) pour le gérer.")
|
| 850 |
+
|
| 851 |
+
st.markdown("---")
|
| 852 |
+
|
| 853 |
+
with st.expander("➕ Créer un Cas d'Usage", expanded=st.session_state.get('show_create_new_use_case_form', False)):
|
| 854 |
+
if not available_families:
|
| 855 |
+
st.caption("Veuillez d'abord créer une famille de métier pour y ajouter des cas d'usage.")
|
| 856 |
+
else:
|
| 857 |
+
if st.button("Afficher/Masquer Formulaire de Création de Cas d'Usage", key="toggle_create_uc_form_in_exp"):
|
| 858 |
+
st.session_state.show_create_new_use_case_form = not st.session_state.get('show_create_new_use_case_form', False)
|
| 859 |
+
st.rerun()
|
| 860 |
+
|
| 861 |
+
if st.session_state.get('show_create_new_use_case_form', False):
|
| 862 |
+
with st.form("new_use_case_form_in_exp", clear_on_submit=True):
|
| 863 |
+
default_create_family_idx_tab = 0
|
| 864 |
+
if current_selected_family_for_edit_logic and current_selected_family_for_edit_logic in available_families:
|
| 865 |
+
default_create_family_idx_tab = available_families.index(current_selected_family_for_edit_logic)
|
| 866 |
+
|
| 867 |
+
uc_parent_family = st.selectbox(
|
| 868 |
+
"Métier parent du nouveau cas d'usage:",
|
| 869 |
+
options=available_families,
|
| 870 |
+
index=default_create_family_idx_tab,
|
| 871 |
+
key="new_uc_parent_fam_in_exp"
|
| 872 |
+
)
|
| 873 |
+
uc_name_input = st.text_input("Nom du Nouveau Cas d'Usage:", key="new_uc_name_in_exp")
|
| 874 |
+
uc_description_input = st.text_area("Description du cas d'usage:", height=100, key="new_uc_desc_in_exp", value="")
|
| 875 |
+
uc_template_input = st.text_area("Template Initial du Cas d'Usage:", height=150, key="new_uc_template_in_exp", value="Nouveau prompt...")
|
| 876 |
+
submitted_new_uc = st.form_submit_button("Créer Cas d'Usage")
|
| 877 |
+
|
| 878 |
+
if submitted_new_uc:
|
| 879 |
+
parent_family_val = uc_parent_family
|
| 880 |
+
uc_name_val = uc_name_input.strip()
|
| 881 |
+
uc_template_val = uc_template_input
|
| 882 |
+
uc_description_val = uc_description_input.strip()
|
| 883 |
+
|
| 884 |
+
if not uc_name_val:
|
| 885 |
+
st.error("Le nom du cas d'usage ne peut pas être vide.")
|
| 886 |
+
elif uc_name_val in st.session_state.editable_prompts.get(parent_family_val, {}):
|
| 887 |
+
st.error(f"Le cas d'usage '{uc_name_val}' existe déjà dans le métier '{parent_family_val}'.")
|
| 888 |
+
else:
|
| 889 |
+
now_iso_create, now_iso_update = get_default_dates()
|
| 890 |
+
st.session_state.editable_prompts[parent_family_val][uc_name_val] = {
|
| 891 |
+
"description": uc_description_val,
|
| 892 |
+
"template": uc_template_val or "Nouveau prompt...",
|
| 893 |
+
"variables": [], "tags": [],
|
| 894 |
+
"usage_count": 0, "created_at": now_iso_create, "updated_at": now_iso_update
|
| 895 |
+
}
|
| 896 |
+
save_editable_prompts_to_local_and_hf()
|
| 897 |
+
st.success(f"Cas d'usage '{uc_name_val}' créé avec succès dans '{parent_family_val}'.")
|
| 898 |
+
st.session_state.show_create_new_use_case_form = False
|
| 899 |
+
st.session_state.force_select_family_name = parent_family_val
|
| 900 |
+
st.session_state.force_select_use_case_name = uc_name_val
|
| 901 |
+
st.session_state.view_mode = "generator"
|
| 902 |
+
st.session_state.active_generated_prompt = ""
|
| 903 |
+
st.rerun()
|
| 904 |
+
|
| 905 |
# --- Tab: Injection (Sidebar content) ---
|
| 906 |
with tab_injection:
|
| 907 |
st.subheader("Assistant & Injection")
|
|
|
|
| 933 |
st.session_state.use_case_selector_edition = st.session_state.force_select_use_case_name
|
| 934 |
st.session_state.force_select_use_case_name = None
|
| 935 |
|
|
|
|
|
|
|
| 936 |
library_family_to_display = st.session_state.get('library_selected_family_for_display')
|
| 937 |
|
| 938 |
# NOUVELLE SECTION POUR LA PAGE D'ACCUEIL
|
|
|
|
| 1183 |
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["created_at"] = now_iso_dup_create
|
| 1184 |
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["updated_at"] = now_iso_dup_update
|
| 1185 |
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["usage_count"] = 0
|
| 1186 |
+
save_editable_prompts_to_local_and_hf()
|
| 1187 |
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}'.")
|
| 1188 |
|
| 1189 |
st.session_state.duplicating_use_case_details = None
|
|
|
|
| 1210 |
deleted_uc_name_for_msg = details['use_case']
|
| 1211 |
deleted_uc_fam_for_msg = details['family']
|
| 1212 |
del st.session_state.editable_prompts[details["family"]][details["use_case"]]
|
| 1213 |
+
save_editable_prompts_to_local_and_hf()
|
| 1214 |
st.success(f"'{deleted_uc_name_for_msg}' supprimé de '{deleted_uc_fam_for_msg}'.")
|
| 1215 |
st.session_state.confirming_delete_details = None
|
| 1216 |
st.rerun()
|
|
|
|
| 1258 |
available_families_check = list(st.session_state.editable_prompts.keys())
|
| 1259 |
if not available_families_check : st.warning("La bibliothèque est entièrement vide. Veuillez créer des métiers et des prompts.")
|
| 1260 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1261 |
|
| 1262 |
elif st.session_state.view_mode == "generator":
|
| 1263 |
generator_family = st.session_state.get('generator_selected_family')
|
|
|
|
| 1414 |
st.balloons()
|
| 1415 |
current_prompt_config["usage_count"] = current_prompt_config.get("usage_count", 0) + 1
|
| 1416 |
current_prompt_config["updated_at"] = datetime.now().isoformat()
|
| 1417 |
+
save_editable_prompts_to_local_and_hf()
|
| 1418 |
|
| 1419 |
except Exception as e:
|
| 1420 |
st.error(f"Erreur inattendue lors de la génération du prompt : {e}")
|
|
|
|
| 1448 |
should_expand_config = SHOW_CONFIG_SECTION and st.session_state.get('go_to_config_section', False)
|
| 1449 |
if SHOW_CONFIG_SECTION:
|
| 1450 |
with st.expander(f"⚙️ Paramétrage du Prompt: {generator_use_case}", expanded=should_expand_config):
|
| 1451 |
+
st.subheader("Template du Prompt")
|
| 1452 |
+
safe_family_key_part = str(generator_family).replace(' ', '_').replace('.', '_').replace('{', '_').replace('}', '_').replace('(', '_').replace(')', '_')
|
| 1453 |
+
safe_uc_key_part = str(generator_use_case).replace(' ', '_').replace('.', '_').replace('{', '_').replace('}', '_').replace('(', '_').replace(')', '_')
|
| 1454 |
+
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)
|
| 1455 |
+
# Description
|
| 1456 |
+
new_desc = st.text_area("Description (aide pour l'utilisateur):", value=current_prompt_config.get('description', ''), height=100, key=f"desc_text_area_{safe_family_key_part}_{safe_uc_key_part}")
|
| 1457 |
+
st.markdown("##### Variables disponibles à insérer :"); variables_config = current_prompt_config.get('variables', [])
|
| 1458 |
+
if not variables_config: st.caption("Aucune variable définie pour ce prompt. Ajoutez-en ci-dessous.")
|
| 1459 |
+
else:
|
| 1460 |
+
for var_info in variables_config:
|
| 1461 |
+
if 'name' in var_info:
|
| 1462 |
+
st.code(f"{{{var_info['name']}}}", language=None)
|
| 1463 |
+
st.caption("Survolez une variable ci-dessus et cliquez sur l'icône qui apparaît pour la copier.")
|
| 1464 |
+
save_template_button_key = f"save_template_button_{safe_family_key_part}_{safe_uc_key_part}"
|
| 1465 |
+
if st.button("Sauvegarder Template & Description", key=save_template_button_key):
|
| 1466 |
+
current_prompt_config['template'] = new_tpl
|
| 1467 |
+
current_prompt_config['description'] = new_desc
|
| 1468 |
+
current_prompt_config["updated_at"] = datetime.now().isoformat()
|
| 1469 |
+
save_editable_prompts_to_local_and_hf()
|
| 1470 |
+
st.success("Template et Description sauvegardés!");
|
| 1471 |
+
st.rerun()
|
| 1472 |
+
st.markdown("---"); st.subheader("Variables du Prompt"); current_variables_list = current_prompt_config.get('variables', [])
|
| 1473 |
+
if not current_variables_list: st.info("Aucune variable définie.")
|
| 1474 |
+
else: pass
|
| 1475 |
+
for idx, var_data in enumerate(list(current_variables_list)):
|
| 1476 |
+
var_id_for_key = var_data.get('name', f"varidx{idx}").replace(" ", "_"); action_key_prefix = f"var_action_{generator_family.replace(' ','_')}_{generator_use_case.replace(' ','_')}_{var_id_for_key}"
|
| 1477 |
+
col_info, col_up, col_down, col_edit, col_delete = st.columns([3, 0.5, 0.5, 0.8, 0.8])
|
| 1478 |
+
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')}`*")
|
| 1479 |
+
with col_up:
|
| 1480 |
+
disable_up_button = (idx == 0)
|
| 1481 |
+
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_local_and_hf(); st.session_state.editing_variable_info = None; st.session_state.variable_type_to_create = None; st.rerun()
|
| 1482 |
+
with col_down:
|
| 1483 |
+
disable_down_button = (idx == len(current_variables_list) - 1)
|
| 1484 |
+
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_local_and_hf(); st.session_state.editing_variable_info = None; st.session_state.variable_type_to_create = None; st.rerun()
|
| 1485 |
+
with col_edit:
|
| 1486 |
+
if st.button("Modifier", key=f"{action_key_prefix}_edit", use_container_width=True): st.session_state.editing_variable_info = { "family": generator_family, "use_case": generator_use_case, "index": idx, "data": copy.deepcopy(var_data) }; st.session_state.variable_type_to_create = var_data.get('type'); st.rerun()
|
| 1487 |
+
with col_delete:
|
| 1488 |
+
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_local_and_hf(); 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()
|
| 1489 |
+
st.markdown("---"); st.subheader("Ajouter une Variable"); is_editing_var = False; variable_data_for_form = {"name": "", "label": "", "type": "", "options": "", "default": ""}
|
| 1490 |
+
if st.session_state.editing_variable_info and st.session_state.editing_variable_info.get("family") == generator_family and st.session_state.editing_variable_info.get("use_case") == generator_use_case:
|
| 1491 |
+
edit_var_idx = st.session_state.editing_variable_info["index"]
|
| 1492 |
+
if edit_var_idx < len(current_prompt_config.get('variables',[])):
|
| 1493 |
+
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))
|
| 1494 |
+
if isinstance(variable_data_for_form.get("options"), list): variable_data_for_form["options"] = ", ".join(map(str, variable_data_for_form["options"]))
|
| 1495 |
+
raw_def_edit_form = variable_data_for_form.get("default")
|
| 1496 |
+
if isinstance(raw_def_edit_form, date): variable_data_for_form["default"] = raw_def_edit_form.strftime("%Y-%m-%d")
|
| 1497 |
+
elif raw_def_edit_form is not None: variable_data_for_form["default"] = str(raw_def_edit_form)
|
| 1498 |
+
else: variable_data_for_form["default"] = ""
|
| 1499 |
+
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
|
| 1500 |
+
if not is_editing_var and st.session_state.variable_type_to_create is None:
|
| 1501 |
+
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
|
| 1502 |
+
for btn_label, type_val in variable_types_map.items():
|
| 1503 |
+
if cols_type_buttons[button_idx % len(cols_type_buttons)].button(btn_label, key=f"btn_type_{type_val}_{generator_use_case.replace(' ','_')}", use_container_width=True): st.session_state.variable_type_to_create = type_val; st.rerun()
|
| 1504 |
+
button_idx += 1
|
| 1505 |
+
st.markdown("---")
|
| 1506 |
+
if st.session_state.variable_type_to_create:
|
| 1507 |
+
current_type_for_form = st.session_state.variable_type_to_create
|
| 1508 |
+
variable_types_map_display = {
|
| 1509 |
+
"text_input": "Zone de texte (courte)", "selectbox": "Liste choix",
|
| 1510 |
+
"date_input": "Date", "number_input": "Nombre", "text_area": "Zone de texte (longue)"
|
| 1511 |
+
}
|
| 1512 |
+
readable_type = variable_types_map_display.get(current_type_for_form, "Type Inconnu")
|
| 1513 |
+
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}"
|
| 1514 |
+
st.markdown(f"##### 2. Configurez la variable")
|
| 1515 |
+
|
| 1516 |
+
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"
|
| 1517 |
+
form_var_specific_key = f"form_var_{current_type_for_form}_{generator_use_case.replace(' ','_')}{form_key_suffix}"
|
| 1518 |
+
|
| 1519 |
+
with st.form(key=form_var_specific_key, clear_on_submit=(not is_editing_var)):
|
| 1520 |
+
st.subheader(form_title)
|
| 1521 |
+
var_name_input_form = st.text_input(
|
| 1522 |
+
"Nom technique (ex : nom_client. Sans espaces, accents ou caractères spéciaux)",
|
| 1523 |
+
value=variable_data_for_form.get("name", ""),
|
| 1524 |
+
key=f"{form_var_specific_key}_name",
|
| 1525 |
+
disabled=is_editing_var
|
| 1526 |
+
)
|
| 1527 |
+
var_label_input_form = st.text_input(
|
| 1528 |
+
"Label pour l'utilisateur (description affichée)",
|
| 1529 |
+
value=variable_data_for_form.get("label", ""),
|
| 1530 |
+
key=f"{form_var_specific_key}_label"
|
| 1531 |
+
)
|
| 1532 |
+
var_options_str_input_form = ""
|
| 1533 |
+
if current_type_for_form == "selectbox":
|
| 1534 |
+
var_options_str_input_form = st.text_input(
|
| 1535 |
+
"Options (séparées par une virgule)",
|
| 1536 |
+
value=variable_data_for_form.get("options", ""),
|
| 1537 |
+
key=f"{form_var_specific_key}_options"
|
| 1538 |
+
)
|
| 1539 |
+
date_hint = " (Format AAAA-MM-JJ)" if current_type_for_form == "date_input" else ""
|
| 1540 |
+
var_default_val_str_input_form = st.text_input(
|
| 1541 |
+
f"Valeur par défaut{date_hint}",
|
| 1542 |
+
value=str(variable_data_for_form.get("default", "")),
|
| 1543 |
+
key=f"{form_var_specific_key}_default"
|
| 1544 |
+
)
|
| 1545 |
+
|
| 1546 |
+
min_val_input_form, max_val_input_form, step_val_input_form, height_val_input_form = None, None, None, None
|
| 1547 |
+
if current_type_for_form == "number_input":
|
| 1548 |
+
num_cols_var_form = st.columns(3)
|
| 1549 |
+
min_val_edit_default = variable_data_for_form.get("min_value")
|
| 1550 |
+
max_val_edit_default = variable_data_for_form.get("max_value")
|
| 1551 |
+
step_val_edit_default = variable_data_for_form.get("step", 1.0)
|
| 1552 |
+
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")
|
| 1553 |
+
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")
|
| 1554 |
+
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")
|
| 1555 |
+
if current_type_for_form == "text_area":
|
| 1556 |
+
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")
|
| 1557 |
+
|
| 1558 |
+
submit_button_label_form = "Sauvegarder Modifications" if is_editing_var else "Ajouter Variable"
|
| 1559 |
+
submitted_specific_var_form = st.form_submit_button(submit_button_label_form)
|
| 1560 |
+
|
| 1561 |
+
if submitted_specific_var_form:
|
| 1562 |
+
var_name_val_submit = var_name_input_form.strip()
|
| 1563 |
+
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.")
|
| 1564 |
+
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.")
|
| 1565 |
+
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.")
|
| 1566 |
+
else:
|
| 1567 |
+
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)
|
| 1568 |
+
if current_type_for_form == "selectbox":
|
| 1569 |
+
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
|
| 1570 |
+
if options_list_submit:
|
| 1571 |
+
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]
|
| 1572 |
+
else: new_var_data_to_submit["default"] = parsed_def_val_submit
|
| 1573 |
+
else: new_var_data_to_submit["default"] = ""
|
| 1574 |
+
else: new_var_data_to_submit["default"] = parsed_def_val_submit
|
| 1575 |
+
if current_type_for_form == "number_input":
|
| 1576 |
+
if min_val_input_form is not None: new_var_data_to_submit["min_value"] = float(min_val_input_form)
|
| 1577 |
+
if max_val_input_form is not None: new_var_data_to_submit["max_value"] = float(max_val_input_form)
|
| 1578 |
+
if step_val_input_form is not None: new_var_data_to_submit["step"] = float(step_val_input_form)
|
| 1579 |
+
else: new_var_data_to_submit["step"] = 1.0
|
| 1580 |
+
if current_type_for_form == "text_area" and height_val_input_form is not None:
|
| 1581 |
+
new_var_data_to_submit["height"] = int(height_val_input_form)
|
| 1582 |
+
|
| 1583 |
+
can_proceed_with_save = True; target_vars_list = current_prompt_config.get('variables', [])
|
| 1584 |
+
if is_editing_var:
|
| 1585 |
+
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
|
| 1586 |
+
else:
|
| 1587 |
+
existing_var_names_in_uc = [v['name'] for v in target_vars_list]
|
| 1588 |
+
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
|
| 1589 |
+
else: target_vars_list.append(new_var_data_to_submit); st.success(f"Variable '{var_name_val_submit}' ajoutée avec succès.")
|
| 1590 |
+
if can_proceed_with_save:
|
| 1591 |
+
current_prompt_config["variables"] = target_vars_list; current_prompt_config["updated_at"] = datetime.now().isoformat(); save_editable_prompts_to_local_and_hf()
|
| 1592 |
+
if not is_editing_var: st.session_state.variable_type_to_create = None
|
| 1593 |
+
st.rerun()
|
| 1594 |
+
|
| 1595 |
+
cancel_button_label_form = "Annuler Modification" if is_editing_var else "Changer de Type / Annuler Création"
|
| 1596 |
+
cancel_btn_key = f"cancel_var_action_btn_{form_var_specific_key}_outside"
|
| 1597 |
+
|
| 1598 |
+
if st.button(cancel_button_label_form, key=cancel_btn_key, help="Réinitialise le formulaire de variable."):
|
| 1599 |
+
st.session_state.variable_type_to_create = None
|
| 1600 |
+
if is_editing_var:
|
| 1601 |
+
st.session_state.editing_variable_info = None
|
| 1602 |
+
st.rerun()
|
| 1603 |
+
st.markdown("---"); st.subheader("🏷️ Tags"); current_tags_str = ", ".join(current_prompt_config.get("tags", []))
|
| 1604 |
+
new_tags_str_input = st.text_input("Tags (séparés par des virgules):", value=current_tags_str, key=f"tags_input_{generator_family}_{generator_use_case}")
|
| 1605 |
+
if st.button("Sauvegarder Tags", key=f"save_tags_btn_{generator_family}_{generator_use_case}"): 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_local_and_hf(); st.success("Tags sauvegardés!"); st.rerun()
|
| 1606 |
+
|
| 1607 |
+
st.markdown("---")
|
| 1608 |
+
st.subheader("Actions sur le Cas d'Usage")
|
| 1609 |
+
|
| 1610 |
+
if st.session_state.duplicating_use_case_details and \
|
| 1611 |
+
st.session_state.duplicating_use_case_details["family"] == generator_family and \
|
| 1612 |
+
st.session_state.duplicating_use_case_details["use_case"] == generator_use_case:
|
| 1613 |
+
|
| 1614 |
+
original_uc_name_for_dup_form = st.session_state.duplicating_use_case_details["use_case"]
|
| 1615 |
+
original_family_name_for_dup = st.session_state.duplicating_use_case_details["family"]
|
| 1616 |
+
st.markdown(f"#### Dupliquer '{original_uc_name_for_dup_form}' (depuis: {original_family_name_for_dup})")
|
| 1617 |
+
|
| 1618 |
+
form_key_duplicate = f"form_duplicate_name_{original_family_name_for_dup.replace(' ','_')}_{original_uc_name_for_dup_form.replace(' ','_')}"
|
| 1619 |
+
with st.form(key=form_key_duplicate):
|
| 1620 |
+
available_families_list = list(st.session_state.editable_prompts.keys())
|
| 1621 |
+
try:
|
| 1622 |
+
default_family_idx = available_families_list.index(original_family_name_for_dup)
|
| 1623 |
+
except ValueError:
|
| 1624 |
+
default_family_idx = 0
|
| 1625 |
+
|
| 1626 |
+
selected_target_family_for_duplicate = st.selectbox(
|
| 1627 |
+
"Choisir la famille de destination pour la copie :",
|
| 1628 |
+
options=available_families_list,
|
| 1629 |
+
index=default_family_idx,
|
| 1630 |
+
key=f"target_family_dup_select_{form_key_duplicate}"
|
| 1631 |
+
)
|
| 1632 |
+
|
| 1633 |
+
suggested_new_name_base = f"{original_uc_name_for_dup_form} (copie)"
|
| 1634 |
+
suggested_new_name = suggested_new_name_base
|
| 1635 |
+
temp_copy_count = 1
|
| 1636 |
+
while suggested_new_name in st.session_state.editable_prompts.get(selected_target_family_for_duplicate, {}):
|
| 1637 |
+
suggested_new_name = f"{suggested_new_name_base} {temp_copy_count}"
|
| 1638 |
+
temp_copy_count += 1
|
| 1639 |
+
|
| 1640 |
+
new_duplicated_uc_name_input = st.text_input(
|
| 1641 |
+
"Nouveau nom pour le cas d'usage dupliqué:",
|
| 1642 |
+
value=suggested_new_name,
|
| 1643 |
+
key=f"new_dup_name_input_{form_key_duplicate}"
|
| 1644 |
+
)
|
| 1645 |
+
|
| 1646 |
+
submitted_duplicate_form = st.form_submit_button("✅ Confirmer la Duplication", use_container_width=True)
|
| 1647 |
+
|
| 1648 |
+
if submitted_duplicate_form:
|
| 1649 |
+
new_uc_name_val_from_form = new_duplicated_uc_name_input.strip()
|
| 1650 |
+
target_family_on_submit = selected_target_family_for_duplicate
|
| 1651 |
+
|
| 1652 |
+
if not new_uc_name_val_from_form:
|
| 1653 |
+
st.error("Le nom du nouveau cas d'usage ne peut pas être vide.")
|
| 1654 |
+
elif new_uc_name_val_from_form in st.session_state.editable_prompts.get(target_family_on_submit, {}):
|
| 1655 |
+
st.error(f"Un cas d'usage nommé '{new_uc_name_val_from_form}' existe déjà dans la famille '{target_family_on_submit}'.")
|
| 1656 |
+
else:
|
| 1657 |
+
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form] = copy.deepcopy(current_prompt_config)
|
| 1658 |
+
now_iso_dup_create, now_iso_dup_update = get_default_dates()
|
| 1659 |
+
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["created_at"] = now_iso_dup_create
|
| 1660 |
+
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["updated_at"] = now_iso_dup_update
|
| 1661 |
+
st.session_state.editable_prompts[target_family_on_submit][new_uc_name_val_from_form]["usage_count"] = 0
|
| 1662 |
+
save_editable_prompts_to_local_and_hf()
|
| 1663 |
+
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}'.")
|
| 1664 |
+
|
| 1665 |
+
st.session_state.duplicating_use_case_details = None
|
| 1666 |
+
st.session_state.force_select_family_name = target_family_on_submit
|
| 1667 |
+
st.session_state.force_select_use_case_name = new_uc_name_val_from_form
|
| 1668 |
+
st.session_state.active_generated_prompt = ""
|
| 1669 |
+
st.session_state.variable_type_to_create = None
|
| 1670 |
+
st.session_state.editing_variable_info = None
|
| 1671 |
+
st.session_state.go_to_config_section = True
|
| 1672 |
+
st.rerun()
|
| 1673 |
+
|
| 1674 |
+
cancel_key_duplicate = f"cancel_dup_process_{original_family_name_for_dup.replace(' ','_')}_{original_uc_name_for_dup_form.replace(' ','_')}"
|
| 1675 |
+
if st.button("❌ Annuler la Duplication", key=cancel_key_duplicate, use_container_width=True):
|
| 1676 |
+
st.session_state.duplicating_use_case_details = None
|
| 1677 |
+
st.rerun()
|
| 1678 |
+
else:
|
| 1679 |
+
action_cols_manage = st.columns(2)
|
| 1680 |
+
with action_cols_manage[0]:
|
| 1681 |
+
dup_key_init = f"initiate_dup_uc_btn_{generator_family.replace(' ','_')}_{generator_use_case.replace(' ','_')}"
|
| 1682 |
+
if st.button("🔄 Dupliquer ce Cas d'Usage", key=dup_key_init, use_container_width=True):
|
| 1683 |
+
st.session_state.duplicating_use_case_details = {
|
| 1684 |
+
"family": generator_family,
|
| 1685 |
+
"use_case": generator_use_case
|
| 1686 |
+
}
|
| 1687 |
+
st.session_state.go_to_config_section = True
|
| 1688 |
+
st.rerun()
|
| 1689 |
+
|
| 1690 |
+
with action_cols_manage[1]:
|
| 1691 |
+
del_uc_key_exp_main = f"del_uc_btn_exp_main_{generator_family.replace(' ','_')}_{generator_use_case.replace(' ','_')}"
|
| 1692 |
+
is_confirming_this_uc_delete_main = bool(st.session_state.confirming_delete_details and \
|
| 1693 |
+
st.session_state.confirming_delete_details.get("family") == generator_family and \
|
| 1694 |
+
st.session_state.confirming_delete_details.get("use_case") == generator_use_case)
|
| 1695 |
+
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):
|
| 1696 |
+
st.session_state.confirming_delete_details = {"family": generator_family, "use_case": generator_use_case}
|
| 1697 |
+
st.rerun()
|
| 1698 |
|
| 1699 |
if st.session_state.get('go_to_config_section'):
|
| 1700 |
st.session_state.go_to_config_section = False
|
|
|
|
| 1770 |
if first_new_uc_name is None:
|
| 1771 |
first_new_uc_name = uc_name_stripped
|
| 1772 |
if successful_injections:
|
| 1773 |
+
save_editable_prompts_to_local_and_hf()
|
| 1774 |
st.success(f"{len(successful_injections)} cas d'usage injectés avec succès dans '{target_family_name}': {', '.join(successful_injections)}")
|
| 1775 |
st.session_state.injection_json_text = ""
|
| 1776 |
if first_new_uc_name:
|
| 1777 |
+
st.session_state.view_mode = "generator"
|
| 1778 |
st.session_state.force_select_family_name = target_family_name
|
| 1779 |
st.session_state.force_select_use_case_name = first_new_uc_name
|
| 1780 |
st.session_state.go_to_config_section = True
|
|
|
|
| 1828 |
st.rerun()
|
| 1829 |
|
| 1830 |
if st.session_state.assistant_mode == "creation":
|
| 1831 |
+
st.markdown("Décrivez votre besoin pour que l'assistant génère une instruction détaillée. Vous donnerez cette instruction à votre LLM qui, en retour, produira les éléments de votre cas d'usage (prompt système, variables, etc.).")
|
| 1832 |
with st.form(key="assistant_creation_form_std"):
|
| 1833 |
# Initialiser current_form_input_values avec les valeurs de session_state ou les valeurs par défaut
|
| 1834 |
# pour que les champs du formulaire soient pré-remplis correctement.
|
|
|
|
| 1877 |
st.session_state.generated_meta_prompt_for_llm = ""
|
| 1878 |
|
| 1879 |
elif st.session_state.assistant_mode == "amelioration":
|
| 1880 |
+
st.markdown("Collez votre prompt existant. L'assistant générera une instruction pour votre LLM afin de transformer votre prompt en un cas d'usage structuré et améliorable pour cette application.")
|
| 1881 |
with st.form(key="assistant_amelioration_form_unified"):
|
| 1882 |
# Utilise la valeur de session_state pour ce champ
|
| 1883 |
prompt_existant_input_val = st.text_area(
|
|
|
|
| 1911 |
if st.session_state.generated_meta_prompt_for_llm:
|
| 1912 |
col_subheader_assist, col_indicator_assist = st.columns([0.85, 0.15])
|
| 1913 |
with col_subheader_assist:
|
| 1914 |
+
st.subheader("📋 Instruction Générée (à coller dans votre LLM) :")
|
| 1915 |
with col_indicator_assist:
|
| 1916 |
st.markdown("<div style='color:red; text-align:right; font-size:0.9em; padding-top:1.9em;padding-right:0.9em;'>Copier ici : 👇</div>", unsafe_allow_html=True)
|
| 1917 |
|
| 1918 |
st.code(st.session_state.generated_meta_prompt_for_llm, language='markdown', line_numbers=True)
|
| 1919 |
st.caption("<span style='color:gray; font-size:0.9em;'>Utilisez l'icône en haut à droite du bloc de code pour copier l'instruction.</span>", unsafe_allow_html=True)
|
| 1920 |
st.markdown("---")
|
| 1921 |
+
st.info("Une fois que 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.")
|
| 1922 |
if st.button("💉 Injecter JSON Manuellement", key="prepare_inject_from_assistant_unified_btn", use_container_width=True, type="primary"):
|
| 1923 |
st.session_state.view_mode = "inject_manual"
|
| 1924 |
st.session_state.injection_selected_family = None
|
|
|
|
| 1927 |
st.rerun()
|
| 1928 |
if not any(st.session_state.editable_prompts.values()): # pragma: no cover
|
| 1929 |
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.")
|
| 1930 |
+
elif st.session_state.view_mode not in ["library", "generator", "inject_manual", "assistant_creation"]: # pragma: no cover
|
| 1931 |
+
st.session_state.view_mode = "library" if list(st.session_state.editable_prompts.keys()) else "generator"
|
| 1932 |
st.rerun()
|
| 1933 |
|
| 1934 |
# --- Sidebar Footer ---
|
| 1935 |
st.sidebar.markdown("---")
|
| 1936 |
+
st.sidebar.info(f"Générateur v3.3.6 - © {CURRENT_YEAR} AIBuilders (démo)")
|