diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 52a9de1..a21278e 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -73,10 +73,18 @@ if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4( # --- HELPER FUNCTIONS --- def slugify(value): - """Erzeugt saubere Dateinamen aus Titeln.""" - value = str(value) + """ + Erzeugt saubere Dateinamen (German-Aware). + z.B. "Müller & Söhne" -> "mueller-und-soehne" + """ + value = str(value).lower() + # Deutsche Umlaute manuell ersetzen, da NFKD sie oft nur strippt + replacements = {'ä': 'ae', 'ö': 'oe', 'ü': 'ue', 'ß': 'ss', '&': 'und'} + for k, v in replacements.items(): + value = value.replace(k, v) + value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii') - value = re.sub(r'[^\w\s-]', '', value).strip().lower() + value = re.sub(r'[^\w\s-]', '', value).strip() return re.sub(r'[-\s]+', '-', value) def normalize_meta_and_body(meta, body): @@ -85,11 +93,9 @@ def normalize_meta_and_body(meta, body): clean_meta = {} extra_content = [] - # Title Normalization if "titel" in meta and "title" not in meta: meta["title"] = meta.pop("titel") - - # Tag Normalization + tag_candidates = ["tags", "emotionale_keywords", "keywords", "schluesselwoerter"] all_tags = [] for key in tag_candidates: @@ -109,7 +115,6 @@ def normalize_meta_and_body(meta, body): extra_content.append(f"## {header}\n{val}\n") if all_tags: - # Bereinige Tags von '#' und Duplikaten clean_tags = [] for t in all_tags: t_clean = str(t).replace("#", "").strip() @@ -125,16 +130,19 @@ def normalize_meta_and_body(meta, body): return clean_meta, final_body def parse_markdown_draft(full_text): - """Robustes Parsing + Sanitization (YAML + Fallbacks).""" - clean_text = full_text + """ + Klassischer Parser (mit YAML-Fixes). + Funktioniert am besten, wenn Struktur grob eingehalten wird. + """ + clean_text = full_text.strip() - # 1. Markdown Code-Blöcke entfernen - pattern_block = r"```(?:markdown|md)?\s*(.*?)\s*```" - match_block = re.search(pattern_block, full_text, re.DOTALL | re.IGNORECASE) + # Code Blocks entfernen + pattern_block = r"```(?:markdown|md|yaml)?\s*(.*?)\s*```" + match_block = re.search(pattern_block, clean_text, re.DOTALL | re.IGNORECASE) if match_block: clean_text = match_block.group(1).strip() - # 2. Split YAML / Body + # Split an '---' parts = re.split(r"^---+\s*$", clean_text, maxsplit=2, flags=re.MULTILINE) meta = {} @@ -144,7 +152,7 @@ def parse_markdown_draft(full_text): yaml_str = parts[1] body_candidate = parts[2] - # FIX 1: Hashtag-Cleaner für YAML (gegen Syntaxfehler) + # YAML Cleanup: Entferne '#' innerhalb der YAML-Sektion yaml_str_clean = yaml_str.replace("#", "") try: @@ -155,14 +163,14 @@ def parse_markdown_draft(full_text): except Exception as e: print(f"YAML Parsing Warning: {e}") body = body_candidate.strip() - - # FIX 3: Titel-Fallback aus H1 + + # Fallback: Titel aus H1 suchen, wenn nicht im YAML if not meta.get("title"): h1_match = re.search(r"^#\s+(.*)$", body, re.MULTILINE) if h1_match: meta["title"] = h1_match.group(1).strip() - - # FIX 4: Type/Status Swap Korrektur + + # Correction: type/status swap if meta.get("type") == "draft": meta["status"] = "draft" meta["type"] = "experience" @@ -171,10 +179,10 @@ def parse_markdown_draft(full_text): def build_markdown_doc(meta, body): """Baut das finale Dokument zusammen.""" - # ID Generation if "id" not in meta or meta["id"] == "generated_on_save": - # Nutze slugify für ID - clean_slug = slugify(meta.get('title', 'note'))[:40] or "note" + # Hier nutzen wir jetzt die verbesserte slugify Funktion + raw_title = meta.get('title', 'note') + clean_slug = slugify(raw_title)[:50] or "note" meta["id"] = f"{datetime.now().strftime('%Y%m%d')}-{clean_slug}" meta["updated"] = datetime.now().strftime("%Y-%m-%d") @@ -258,7 +266,7 @@ def submit_feedback(query_id, node_id, score, comment=None): def render_sidebar(): with st.sidebar: st.title("🧠 mindnet") - st.caption("v2.4.2 | Robust UI") + st.caption("v2.4.3 | Filename Fix") mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0) st.divider() st.subheader("⚙️ Settings") @@ -298,7 +306,7 @@ def render_draft_editor(msg): st.session_state[data_sugg_key] = [] st.session_state[data_body_key] = body.strip() - # Widgets Init + # Init Widgets Keys (Resurrection) st.session_state[f"{key_base}_wdg_title"] = meta["title"] st.session_state[f"{key_base}_wdg_type"] = meta["type"] st.session_state[f"{key_base}_wdg_tags"] = meta["tags_str"] @@ -412,11 +420,11 @@ def render_draft_editor(msg): final_tags_str = st.session_state.get(f"{key_base}_wdg_tags", "") final_tags = [t.strip() for t in final_tags_str.split(",") if t.strip()] + # WICHTIG: Hier ziehen wir die Daten explizit aus dem Widget-State final_meta = { "id": "generated_on_save", "type": st.session_state.get(f"{key_base}_wdg_type", "default"), - # Title mit Fallback (Widget > Meta > Untitled) - "title": st.session_state.get(f"{key_base}_wdg_title", meta_ref.get("title", "Untitled")), + "title": st.session_state.get(f"{key_base}_wdg_title", "Untitled"), "status": "draft", "tags": final_tags } @@ -435,9 +443,12 @@ def render_draft_editor(msg): with b1: if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"): with st.spinner("Speichere im Vault..."): + + # DATEINAMEN-LOGIK (Fix) raw_title = final_meta.get("title", "draft") - # Slugify für saubere Dateinamen - safe_title = slugify(raw_title)[:40] or "draft" + safe_title = slugify(raw_title)[:50] + 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)