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):