From 2677ad7269b0216939e8530df39cbbd4059cef3a Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 11 Dec 2025 13:15:43 +0100 Subject: [PATCH] bug fix ui --- app/frontend/ui.py | 96 ++++++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 26154ec..8be766e 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.7", page_icon="🧠", layout="wide") +st.set_page_config(page_title="mindnet v2.3.8", page_icon="🧠", layout="wide") # --- CSS STYLING --- st.markdown(""" @@ -112,6 +112,7 @@ def normalize_meta_and_body(meta, body): def parse_markdown_draft(full_text): """Robustes Parsing + Sanitization.""" clean_text = full_text + pattern_block = r"```(?:markdown|md)?\s*(.*?)\s*```" match_block = re.search(pattern_block, full_text, re.DOTALL | re.IGNORECASE) if match_block: @@ -188,6 +189,7 @@ def send_chat_message(message: str, top_k: int, explain: bool): return {"error": str(e)} def analyze_draft_text(text: str, n_type: str): + """Ruft den neuen Intelligence-Service (WP-11) auf.""" try: response = requests.post( INGEST_ANALYZE_ENDPOINT, @@ -200,11 +202,12 @@ def analyze_draft_text(text: str, n_type: str): return {"error": str(e)} def save_draft_to_vault(markdown_content: str, filename: str = None): + """Ruft den neuen Persistence-Service (WP-11) auf.""" try: response = requests.post( INGEST_SAVE_ENDPOINT, json={"markdown_content": markdown_content, "filename": filename}, - timeout=60 + timeout=60 # Indizierung kann dauern ) response.raise_for_status() return response.json() @@ -222,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.7 | Stable State") + st.caption("v2.3.8 | Fixed State Sync") mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0) st.divider() st.subheader("⚙️ Settings") @@ -241,42 +244,38 @@ def render_draft_editor(msg): key_base = f"draft_{qid}" # State Keys - data_body_key = f"{key_base}_data_body" # Persistenter Speicher - data_meta_key = f"{key_base}_data_meta" # Metadaten - data_sugg_key = f"{key_base}_data_suggestions" # Vorschläge - widget_body_key = f"{key_base}_widget_body" # Das Textfeld selbst + data_meta_key = f"{key_base}_data_meta" + data_sugg_key = f"{key_base}_data_suggestions" + widget_body_key = f"{key_base}_widget_body" - # --- 1. INIT STATE --- + # --- 1. INIT STATE (Nur einmalig) --- if f"{key_base}_init" not in st.session_state: meta, body = parse_markdown_draft(msg["content"]) - # Metadata Defaults + # Defaults if "type" not in meta: meta["type"] = "default" if "title" not in meta: meta["title"] = "" tags = meta.get("tags", []) meta["tags_str"] = ", ".join(tags) if isinstance(tags, list) else str(tags) - # Init Session State st.session_state[data_meta_key] = meta - st.session_state[data_body_key] = body.strip() st.session_state[data_sugg_key] = [] + + # WICHTIG: Wir initialisieren den Widget Key direkt! + st.session_state[widget_body_key] = body.strip() + st.session_state[f"{key_base}_init"] = True - # --- CALLBACKS --- - def _sync_body(): - # Schreibt Widget-Inhalt in den Speicher - st.session_state[data_body_key] = st.session_state[widget_body_key] - + # --- CALLBACKS (Modifizieren direkt den Widget-Key) --- + def _insert_text(text_to_insert): - # Liest vom Widget (aktuellster Stand!), fügt an, schreibt zurück - current = st.session_state[widget_body_key] - st.session_state[data_body_key] = f"{current}\n\n{text_to_insert}" - # Wichtig: Leere Vorschläge nach Insert, damit man nicht doppelt klickt - # st.session_state[data_sugg_key] = [] + 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 def _remove_text(text_to_remove): current = st.session_state[widget_body_key] - st.session_state[data_body_key] = current.replace(text_to_remove, "").strip() + st.session_state[widget_body_key] = current.replace(text_to_remove, "").strip() # --- UI LAYOUT --- st.markdown(f'
', unsafe_allow_html=True) @@ -300,12 +299,11 @@ def render_draft_editor(msg): # --- TAB 1: EDITOR --- with tab_edit: - # Value kommt aus Data, Änderungen gehen via Callback zurück in Data + # State-Fix: Wir übergeben KEIN 'value' Argument, wenn der Key existiert. + # So hat das Widget Vorrang vor veraltetem Code. st.text_area( "Body", key=widget_body_key, - value=st.session_state[data_body_key], - on_change=_sync_body, height=500, label_visibility="collapsed" ) @@ -315,16 +313,14 @@ def render_draft_editor(msg): st.info("Klicke auf 'Analysieren', um Verknüpfungen für den AKTUELLEN Text zu finden.") if st.button("🔍 Analyse starten", key=f"{key_base}_analyze"): - # 1. Reset + # 1. Reset suggestions st.session_state[data_sugg_key] = [] - # 2. Text holen (HIER WAR DER FEHLER) - # Wir holen ihn direkt aus dem Widget-Key, da dieser immer aktuell ist, - # auch wenn on_change noch nicht gefeuert hat. - text_to_analyze = st.session_state.get(widget_body_key, st.session_state[data_body_key]) + # 2. Text DIREKT aus dem Widget State lesen + text_to_analyze = st.session_state[widget_body_key] - # Debug (optional, damit du siehst was passiert) - # st.caption(f"Sende {len(text_to_analyze)} Zeichen an API...") + # 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"]) @@ -335,7 +331,7 @@ def render_draft_editor(msg): suggestions = analysis.get("suggestions", []) st.session_state[data_sugg_key] = suggestions if not suggestions: - st.warning("Keine Vorschläge gefunden (Text zu kurz oder keine Matches).") + st.warning("Keine Vorschläge gefunden.") else: st.success(f"{len(suggestions)} Vorschläge gefunden.") @@ -343,7 +339,7 @@ def render_draft_editor(msg): suggestions = st.session_state[data_sugg_key] if suggestions: # Hole aktuellen Text für Vergleich - current_text_state = st.session_state.get(widget_body_key, "") + current_text_state = st.session_state[widget_body_key] for idx, sugg in enumerate(suggestions): link_text = sugg.get('suggested_markdown', '') @@ -375,7 +371,7 @@ def render_draft_editor(msg): "tags": final_tags } # Auch hier: Nimm den aktuellsten Text für die Vorschau - final_body = st.session_state.get(widget_body_key, st.session_state[data_body_key]) + final_body = st.session_state[widget_body_key] final_doc = build_markdown_doc(final_meta, final_body) with tab_view: @@ -385,13 +381,25 @@ def render_draft_editor(msg): st.markdown("---") - if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"): - with st.spinner("Speichere..."): - 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" - res = save_draft_to_vault(final_doc, filename=fname) - if "error" in res: st.error(f"Fehler: {res['error']}") - else: st.success(f"Gespeichert: {res.get('file_path')}") + b1, b2 = st.columns([1, 1]) + with b1: + if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"): + with st.spinner("Speichere..."): + 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']}") + else: + st.success(f"Gespeichert: {result.get('file_path')}") + st.balloons() + with b2: + if st.button("📋 Code anzeigen", key=f"{key_base}_btn_copy"): + st.code(final_doc, language="markdown") + + st.markdown("
", unsafe_allow_html=True) def render_chat_interface(top_k, explain): @@ -453,11 +461,9 @@ def render_chat_interface(top_k, explain): st.rerun() def render_manual_editor(): - # Wir nutzen dieselbe Logik wie beim Interview, aber mit einem "leeren" Mock-Objekt - # Wichtig: Feste Query-ID für Manuellen Modus, damit der State persistent bleibt mock_msg = { "content": "---\ntype: concept\ntitle: Neue Notiz\nstatus: draft\ntags: []\n---\n# Titel\n", - "query_id": "manual_editor_fixed_v1" + "query_id": "manual_mode_v2" } render_draft_editor(mock_msg)