diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 8be766e..b68014c 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -23,7 +23,7 @@ timeout_setting = os.getenv("MINDNET_API_TIMEOUT") or os.getenv("MINDNET_LLM_TIM API_TIMEOUT = float(timeout_setting) if timeout_setting else 300.0 # --- PAGE SETUP --- -st.set_page_config(page_title="mindnet v2.3.8", page_icon="🧠", layout="wide") +st.set_page_config(page_title="mindnet v2.3.9", page_icon="🧠", layout="wide") # --- CSS STYLING --- st.markdown(""" @@ -225,7 +225,7 @@ def submit_feedback(query_id, node_id, score, comment=None): def render_sidebar(): with st.sidebar: st.title("🧠 mindnet") - st.caption("v2.3.8 | Fixed State Sync") + st.caption("v2.3.9 | Stable ID Fix") mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0) st.divider() st.subheader("⚙️ Settings") @@ -240,15 +240,23 @@ def render_sidebar(): return mode, top_k, explain def render_draft_editor(msg): - qid = msg.get('query_id', str(uuid.uuid4())) + # --- STABLE ID FIX (Der entscheidende Teil) --- + # Wir prüfen, ob die Nachricht schon eine ID hat. Wenn nicht, erzeugen wir eine + # und SPEICHERN sie zurück in das msg-Objekt (das Teil von session_state ist). + # So bleibt die ID über Reruns hinweg identisch. + if "query_id" not in msg or not msg["query_id"]: + msg["query_id"] = str(uuid.uuid4()) + + qid = msg["query_id"] key_base = f"draft_{qid}" # State Keys data_meta_key = f"{key_base}_data_meta" data_sugg_key = f"{key_base}_data_suggestions" widget_body_key = f"{key_base}_widget_body" + data_body_key = f"{key_base}_data_body" - # --- 1. INIT STATE (Nur einmalig) --- + # --- 1. INIT STATE (Nur einmalig pro stabiler ID) --- if f"{key_base}_init" not in st.session_state: meta, body = parse_markdown_draft(msg["content"]) @@ -258,24 +266,32 @@ def render_draft_editor(msg): tags = meta.get("tags", []) meta["tags_str"] = ", ".join(tags) if isinstance(tags, list) else str(tags) + # Persistent Data st.session_state[data_meta_key] = meta st.session_state[data_sugg_key] = [] + st.session_state[data_body_key] = body.strip() - # WICHTIG: Wir initialisieren den Widget Key direkt! + # Widget Init (wichtig: Hier wird der "Default Value" des Widgets gesetzt) st.session_state[widget_body_key] = body.strip() st.session_state[f"{key_base}_init"] = True - # --- CALLBACKS (Modifizieren direkt den Widget-Key) --- - + # --- CALLBACKS --- + def _sync_body(): + # Sync vom Widget zurück in den persistenten Speicher + st.session_state[data_body_key] = st.session_state[widget_body_key] + def _insert_text(text_to_insert): + # Einfügen in Widget State current = st.session_state[widget_body_key] st.session_state[widget_body_key] = f"{current}\n\n{text_to_insert}" - # Kein Rerun nötig, Button-Klick macht das implizit + # Sync auch data_body + st.session_state[data_body_key] = st.session_state[widget_body_key] def _remove_text(text_to_remove): current = st.session_state[widget_body_key] st.session_state[widget_body_key] = current.replace(text_to_remove, "").strip() + st.session_state[data_body_key] = st.session_state[widget_body_key] # --- UI LAYOUT --- st.markdown(f'
', unsafe_allow_html=True) @@ -299,12 +315,13 @@ def render_draft_editor(msg): # --- TAB 1: EDITOR --- with tab_edit: - # State-Fix: Wir übergeben KEIN 'value' Argument, wenn der Key existiert. - # So hat das Widget Vorrang vor veraltetem Code. + # Hier kein 'value=' übergeben, wenn der Key im Session State ist. + # Streamlit nimmt automatisch den Wert aus session_state[widget_body_key]. st.text_area( "Body", key=widget_body_key, height=500, + on_change=_sync_body, label_visibility="collapsed" ) @@ -316,12 +333,9 @@ def render_draft_editor(msg): # 1. Reset suggestions st.session_state[data_sugg_key] = [] - # 2. Text DIREKT aus dem Widget State lesen + # 2. Text DIREKT aus dem Widget State lesen (das ist der aktuellste Stand im Browser) text_to_analyze = st.session_state[widget_body_key] - # 3. Debug Output (optional) - # st.info(f"Sende {len(text_to_analyze)} Zeichen an API...") - with st.spinner("Analysiere..."): analysis = analyze_draft_text(text_to_analyze, meta_ref["type"]) @@ -338,7 +352,6 @@ def render_draft_editor(msg): # Render List suggestions = st.session_state[data_sugg_key] if suggestions: - # Hole aktuellen Text für Vergleich current_text_state = st.session_state[widget_body_key] for idx, sugg in enumerate(suggestions): @@ -370,7 +383,7 @@ def render_draft_editor(msg): "status": "draft", "tags": final_tags } - # Auch hier: Nimm den aktuellsten Text für die Vorschau + # Nimm den Widget Content final_body = st.session_state[widget_body_key] final_doc = build_markdown_doc(final_meta, final_body) @@ -384,11 +397,10 @@ def render_draft_editor(msg): b1, b2 = st.columns([1, 1]) with b1: if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"): - with st.spinner("Speichere..."): + with st.spinner("Speichere im Vault..."): safe_title = re.sub(r'[^a-zA-Z0-9]', '-', meta_ref["title"]).lower()[:30] or "draft" fname = f"{datetime.now().strftime('%Y%m%d')}-{safe_title}.md" - # Sende das finale Dokument an die API result = save_draft_to_vault(final_doc, filename=fname) if "error" in result: st.error(f"Fehler: {result['error']}")