WP15 #9

Merged
Lars merged 54 commits from WP15 into main 2025-12-13 06:39:48 +01:00
Showing only changes of commit fb6e35ed01 - Show all commits

View File

@ -73,10 +73,18 @@ if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4(
# --- HELPER FUNCTIONS --- # --- HELPER FUNCTIONS ---
def slugify(value): 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 = 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) return re.sub(r'[-\s]+', '-', value)
def normalize_meta_and_body(meta, body): def normalize_meta_and_body(meta, body):
@ -85,11 +93,9 @@ def normalize_meta_and_body(meta, body):
clean_meta = {} clean_meta = {}
extra_content = [] extra_content = []
# Title Normalization
if "titel" in meta and "title" not in meta: if "titel" in meta and "title" not in meta:
meta["title"] = meta.pop("titel") meta["title"] = meta.pop("titel")
# Tag Normalization
tag_candidates = ["tags", "emotionale_keywords", "keywords", "schluesselwoerter"] tag_candidates = ["tags", "emotionale_keywords", "keywords", "schluesselwoerter"]
all_tags = [] all_tags = []
for key in tag_candidates: for key in tag_candidates:
@ -109,7 +115,6 @@ def normalize_meta_and_body(meta, body):
extra_content.append(f"## {header}\n{val}\n") extra_content.append(f"## {header}\n{val}\n")
if all_tags: if all_tags:
# Bereinige Tags von '#' und Duplikaten
clean_tags = [] clean_tags = []
for t in all_tags: for t in all_tags:
t_clean = str(t).replace("#", "").strip() t_clean = str(t).replace("#", "").strip()
@ -125,16 +130,19 @@ def normalize_meta_and_body(meta, body):
return clean_meta, final_body return clean_meta, final_body
def parse_markdown_draft(full_text): 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 # Code Blocks entfernen
pattern_block = r"```(?:markdown|md)?\s*(.*?)\s*```" pattern_block = r"```(?:markdown|md|yaml)?\s*(.*?)\s*```"
match_block = re.search(pattern_block, full_text, re.DOTALL | re.IGNORECASE) match_block = re.search(pattern_block, clean_text, re.DOTALL | re.IGNORECASE)
if match_block: if match_block:
clean_text = match_block.group(1).strip() 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) parts = re.split(r"^---+\s*$", clean_text, maxsplit=2, flags=re.MULTILINE)
meta = {} meta = {}
@ -144,7 +152,7 @@ def parse_markdown_draft(full_text):
yaml_str = parts[1] yaml_str = parts[1]
body_candidate = parts[2] 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("#", "") yaml_str_clean = yaml_str.replace("#", "")
try: try:
@ -155,14 +163,14 @@ def parse_markdown_draft(full_text):
except Exception as e: except Exception as e:
print(f"YAML Parsing Warning: {e}") print(f"YAML Parsing Warning: {e}")
body = body_candidate.strip() body = body_candidate.strip()
# FIX 3: Titel-Fallback aus H1 # Fallback: Titel aus H1 suchen, wenn nicht im YAML
if not meta.get("title"): if not meta.get("title"):
h1_match = re.search(r"^#\s+(.*)$", body, re.MULTILINE) h1_match = re.search(r"^#\s+(.*)$", body, re.MULTILINE)
if h1_match: if h1_match:
meta["title"] = h1_match.group(1).strip() meta["title"] = h1_match.group(1).strip()
# FIX 4: Type/Status Swap Korrektur # Correction: type/status swap
if meta.get("type") == "draft": if meta.get("type") == "draft":
meta["status"] = "draft" meta["status"] = "draft"
meta["type"] = "experience" meta["type"] = "experience"
@ -171,10 +179,10 @@ def parse_markdown_draft(full_text):
def build_markdown_doc(meta, body): def build_markdown_doc(meta, body):
"""Baut das finale Dokument zusammen.""" """Baut das finale Dokument zusammen."""
# ID Generation
if "id" not in meta or meta["id"] == "generated_on_save": if "id" not in meta or meta["id"] == "generated_on_save":
# Nutze slugify für ID # Hier nutzen wir jetzt die verbesserte slugify Funktion
clean_slug = slugify(meta.get('title', 'note'))[:40] or "note" 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["id"] = f"{datetime.now().strftime('%Y%m%d')}-{clean_slug}"
meta["updated"] = datetime.now().strftime("%Y-%m-%d") 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(): def render_sidebar():
with st.sidebar: with st.sidebar:
st.title("🧠 mindnet") 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) mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0)
st.divider() st.divider()
st.subheader("⚙️ Settings") st.subheader("⚙️ Settings")
@ -298,7 +306,7 @@ def render_draft_editor(msg):
st.session_state[data_sugg_key] = [] st.session_state[data_sugg_key] = []
st.session_state[data_body_key] = body.strip() 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_title"] = meta["title"]
st.session_state[f"{key_base}_wdg_type"] = meta["type"] st.session_state[f"{key_base}_wdg_type"] = meta["type"]
st.session_state[f"{key_base}_wdg_tags"] = meta["tags_str"] 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_str = st.session_state.get(f"{key_base}_wdg_tags", "")
final_tags = [t.strip() for t in final_tags_str.split(",") if t.strip()] 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 = { final_meta = {
"id": "generated_on_save", "id": "generated_on_save",
"type": st.session_state.get(f"{key_base}_wdg_type", "default"), "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", "Untitled"),
"title": st.session_state.get(f"{key_base}_wdg_title", meta_ref.get("title", "Untitled")),
"status": "draft", "status": "draft",
"tags": final_tags "tags": final_tags
} }
@ -435,9 +443,12 @@ def render_draft_editor(msg):
with b1: with b1:
if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"): if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"):
with st.spinner("Speichere im Vault..."): with st.spinner("Speichere im Vault..."):
# DATEINAMEN-LOGIK (Fix)
raw_title = final_meta.get("title", "draft") raw_title = final_meta.get("title", "draft")
# Slugify für saubere Dateinamen safe_title = slugify(raw_title)[:50]
safe_title = slugify(raw_title)[:40] or "draft" if not safe_title: safe_title = "draft"
fname = f"{datetime.now().strftime('%Y%m%d')}-{safe_title}.md" fname = f"{datetime.now().strftime('%Y%m%d')}-{safe_title}.md"
result = save_draft_to_vault(final_doc, filename=fname) result = save_draft_to_vault(final_doc, filename=fname)