diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 20bb3f9..26154ec 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -240,80 +240,67 @@ def render_draft_editor(msg): qid = msg.get('query_id', str(uuid.uuid4())) key_base = f"draft_{qid}" - # === STATE MANAGEMENT KEYS === - # Wir nutzen getrennte Keys für Widget und Daten, um Streamlit's Bereinigung zu umgehen. - # Persistent Keys (bleiben erhalten auch beim Tab-Wechsel) - data_body_key = f"{key_base}_data_body" - data_meta_key = f"{key_base}_data_meta" - data_sugg_key = f"{key_base}_data_suggestions" - - # Widget Keys (können sich ändern/neu gezeichnet werden) - widget_body_key = f"{key_base}_widget_body" + # 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 - # --- 1. INIT STATE (Einmalig pro Nachricht) --- + # --- 1. INIT STATE --- if f"{key_base}_init" not in st.session_state: meta, body = parse_markdown_draft(msg["content"]) - # Defaults setzen + # Metadata Defaults if "type" not in meta: meta["type"] = "default" if "title" not in meta: meta["title"] = "" - if "tags" not in meta: meta["tags"] = [] - - # Tags Listen-Check - if isinstance(meta["tags"], list): - meta["tags_str"] = ", ".join(meta["tags"]) - else: - meta["tags_str"] = str(meta.get("tags", "")) + tags = meta.get("tags", []) + meta["tags_str"] = ", ".join(tags) if isinstance(tags, list) else str(tags) - # Persistent speichern + # Init Session State st.session_state[data_meta_key] = meta st.session_state[data_body_key] = body.strip() st.session_state[data_sugg_key] = [] st.session_state[f"{key_base}_init"] = True - # --- HELPER CALLBACKS --- - # Sync Widget -> Data + # --- CALLBACKS --- def _sync_body(): + # Schreibt Widget-Inhalt in den Speicher st.session_state[data_body_key] = st.session_state[widget_body_key] - # Insert Text (Daten ändern) def _insert_text(text_to_insert): - current = st.session_state[data_body_key] + # 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] = [] + def _remove_text(text_to_remove): - current = st.session_state[data_body_key] + current = st.session_state[widget_body_key] st.session_state[data_body_key] = current.replace(text_to_remove, "").strip() - # --- 2. UI LAYOUT --- + # --- UI LAYOUT --- st.markdown(f'
', unsafe_allow_html=True) st.markdown("### 📝 Entwurf bearbeiten") - # Load Data Reference - meta_ref = st.session_state[data_meta_key] - # Metadata Form + meta_ref = st.session_state[data_meta_key] c1, c2 = st.columns([2, 1]) with c1: - new_title = st.text_input("Titel", key=f"{key_base}_wdg_title", value=meta_ref["title"]) - meta_ref["title"] = new_title # Direct Sync + meta_ref["title"] = st.text_input("Titel", key=f"{key_base}_wdg_title", value=meta_ref["title"]) with c2: - known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle", "default"] - curr_type = meta_ref["type"] - if curr_type not in known_types: known_types.append(curr_type) - new_type = st.selectbox("Typ", known_types, index=known_types.index(curr_type), key=f"{key_base}_wdg_type") - meta_ref["type"] = new_type # Direct Sync + known_types = ["concept", "project", "decision", "experience", "journal", "value", "goal", "principle"] + curr = meta_ref["type"] + if curr not in known_types: known_types.append(curr) + meta_ref["type"] = st.selectbox("Typ", known_types, index=known_types.index(curr), key=f"{key_base}_wdg_type") - new_tags = st.text_input("Tags", key=f"{key_base}_wdg_tags", value=meta_ref.get("tags_str", "")) - meta_ref["tags_str"] = new_tags # Direct Sync + meta_ref["tags_str"] = st.text_input("Tags", key=f"{key_base}_wdg_tags", value=meta_ref.get("tags_str", "")) # Tabs tab_edit, tab_intel, tab_view = st.tabs(["✏️ Inhalt", "🧠 Intelligence", "👁️ Vorschau"]) # --- TAB 1: EDITOR --- with tab_edit: - # Hier ist der Trick: Value kommt aus 'data_body_key', - # Änderungen triggern '_sync_body', der zurück in 'data_body_key' schreibt. + # Value kommt aus Data, Änderungen gehen via Callback zurück in Data st.text_area( "Body", key=widget_body_key, @@ -328,12 +315,18 @@ 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. Alte Ergebnisse löschen für Feedback + # 1. Reset 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]) + + # Debug (optional, damit du siehst was passiert) + # st.caption(f"Sende {len(text_to_analyze)} Zeichen an API...") + with st.spinner("Analysiere..."): - # Aktuellen Text nehmen - text_to_analyze = st.session_state[data_body_key] analysis = analyze_draft_text(text_to_analyze, meta_ref["type"]) if "error" in analysis: @@ -342,23 +335,27 @@ 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.") + st.warning("Keine Vorschläge gefunden (Text zu kurz oder keine Matches).") else: st.success(f"{len(suggestions)} Vorschläge gefunden.") + # Render List 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, "") + for idx, sugg in enumerate(suggestions): link_text = sugg.get('suggested_markdown', '') - is_inserted = link_text in st.session_state[data_body_key] + is_inserted = link_text in current_text_state - card_style = "border-left: 3px solid #28a745;" if is_inserted else "border-left: 3px solid #1a73e8;" bg_color = "#e6fffa" if is_inserted else "#ffffff" + border = "3px solid #28a745" if is_inserted else "3px solid #1a73e8" st.markdown(f""" -
- {sugg.get('target_title', 'Unbekannt')} ({sugg.get('type', 'semantic')})
- {sugg.get('reason', 'N/A')}
+
+ {sugg.get('target_title')} ({sugg.get('type')})
+ {sugg.get('reason')}
{link_text}
""", unsafe_allow_html=True) @@ -368,17 +365,18 @@ def render_draft_editor(msg): else: st.button("➕ Einfügen", key=f"add_{idx}_{key_base}", on_click=_insert_text, args=(link_text,)) - # --- TAB 3: PREVIEW & SAVE --- - # Final Assembly - final_tags_list = [t.strip() for t in meta_ref["tags_str"].split(",") if t.strip()] + # --- TAB 3: SAVE --- + final_tags = [t.strip() for t in meta_ref["tags_str"].split(",") if t.strip()] final_meta = { "id": "generated_on_save", "type": meta_ref["type"], "title": meta_ref["title"], "status": "draft", - "tags": final_tags_list + "tags": final_tags } - final_doc = build_markdown_doc(final_meta, st.session_state[data_body_key]) + # 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_doc = build_markdown_doc(final_meta, final_body) with tab_view: st.markdown('
', unsafe_allow_html=True) @@ -387,25 +385,13 @@ def render_draft_editor(msg): st.markdown("---") - b1, b2 = st.columns([1, 1]) - with b1: - if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"): - with st.spinner("Speichere im Vault..."): - safe_title = re.sub(r'[^a-zA-Z0-9]', '-', meta_ref["title"]).lower()[:30] - if not safe_title: safe_title = "draft" - fname = f"{datetime.now().strftime('%Y%m%d')}-{safe_title}.md" - - 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) + 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')}") def render_chat_interface(top_k, explain):