This commit is contained in:
Lars 2025-12-12 17:31:03 +01:00
parent fb6e35ed01
commit 4cd82d1d2b

View File

@ -1,3 +1,9 @@
# app/frontend/ui.py
# ... (Imports und Setup bleiben gleich) ...
# Ersetze die gesamte Datei mit diesem Inhalt:
import streamlit as st import streamlit as st
import requests import requests
import uuid import uuid
@ -54,15 +60,6 @@ st.markdown("""
background-color: white; background-color: white;
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
} }
.suggestion-card {
border-left: 3px solid #1a73e8;
background-color: #ffffff;
padding: 10px;
margin-bottom: 8px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
</style> </style>
""", unsafe_allow_html=True) """, unsafe_allow_html=True)
@ -77,9 +74,9 @@ def slugify(value):
Erzeugt saubere Dateinamen (German-Aware). Erzeugt saubere Dateinamen (German-Aware).
z.B. "Müller & Söhne" -> "mueller-und-soehne" z.B. "Müller & Söhne" -> "mueller-und-soehne"
""" """
if not value: return ""
value = str(value).lower() value = str(value).lower()
# Deutsche Umlaute manuell ersetzen, da NFKD sie oft nur strippt replacements = {'ä': 'ae', 'ö': 'oe', 'ü': 'ue', 'ß': 'ss', '&': 'und', '+': 'und'}
replacements = {'ä': 'ae', 'ö': 'oe', 'ü': 'ue', 'ß': 'ss', '&': 'und'}
for k, v in replacements.items(): for k, v in replacements.items():
value = value.replace(k, v) value = value.replace(k, v)
@ -130,19 +127,16 @@ 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)."""
Klassischer Parser (mit YAML-Fixes).
Funktioniert am besten, wenn Struktur grob eingehalten wird.
"""
clean_text = full_text.strip() clean_text = full_text.strip()
# Code Blocks entfernen # 1. Markdown Code-Blöcke entfernen
pattern_block = r"```(?:markdown|md|yaml)?\s*(.*?)\s*```" pattern_block = r"```(?:markdown|md|yaml)?\s*(.*?)\s*```"
match_block = re.search(pattern_block, clean_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()
# Split an '---' # 2. Split YAML / Body
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 = {}
@ -152,9 +146,8 @@ def parse_markdown_draft(full_text):
yaml_str = parts[1] yaml_str = parts[1]
body_candidate = parts[2] body_candidate = parts[2]
# YAML Cleanup: Entferne '#' innerhalb der YAML-Sektion # YAML Cleanup
yaml_str_clean = yaml_str.replace("#", "") yaml_str_clean = yaml_str.replace("#", "")
try: try:
parsed = yaml.safe_load(yaml_str_clean) parsed = yaml.safe_load(yaml_str_clean)
if isinstance(parsed, dict): if isinstance(parsed, dict):
@ -180,7 +173,6 @@ 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."""
if "id" not in meta or meta["id"] == "generated_on_save": if "id" not in meta or meta["id"] == "generated_on_save":
# Hier nutzen wir jetzt die verbesserte slugify Funktion
raw_title = meta.get('title', 'note') raw_title = meta.get('title', 'note')
clean_slug = slugify(raw_title)[:50] or "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}"
@ -266,7 +258,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.3 | Filename Fix") st.caption("v2.4.4 | Smart Filename")
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")
@ -297,7 +289,7 @@ def render_draft_editor(msg):
if f"{key_base}_init" not in st.session_state: if f"{key_base}_init" not in st.session_state:
meta, body = parse_markdown_draft(msg["content"]) meta, body = parse_markdown_draft(msg["content"])
if "type" not in meta: meta["type"] = "default" if "type" not in meta: meta["type"] = "default"
if "title" not in meta: meta["title"] = "" if "title" not in meta: meta["title"] = "" # Kann leer sein
tags = meta.get("tags", []) tags = meta.get("tags", [])
meta["tags_str"] = ", ".join(tags) if isinstance(tags, list) else str(tags) meta["tags_str"] = ", ".join(tags) if isinstance(tags, list) else str(tags)
@ -306,7 +298,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()
# Init Widgets Keys (Resurrection) # Init Widgets Keys
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"]
@ -420,16 +412,25 @@ 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 # Live Daten aus Widget (dies ist die Wahrheit!)
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": st.session_state.get(f"{key_base}_wdg_title", "Untitled"), "title": st.session_state.get(f"{key_base}_wdg_title", "").strip(),
"status": "draft", "status": "draft",
"tags": final_tags "tags": final_tags
} }
final_body = st.session_state.get(widget_body_key, st.session_state[data_body_key]) final_body = st.session_state.get(widget_body_key, st.session_state[data_body_key])
# 1. Update Title in Meta (damit es im YAML landet)
if not final_meta["title"]:
# Fallback auf H1 im Text
h1_match = re.search(r"^#\s+(.*)$", final_body, re.MULTILINE)
if h1_match:
final_meta["title"] = h1_match.group(1).strip()
# 2. Build Doc
final_doc = build_markdown_doc(final_meta, final_body) final_doc = build_markdown_doc(final_meta, final_body)
with tab_view: with tab_view:
@ -444,12 +445,20 @@ def render_draft_editor(msg):
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) # --- DATEINAMEN INTELLIGENZ ---
raw_title = final_meta.get("title", "draft") # Prio 1: Meta Titel
safe_title = slugify(raw_title)[:50] title_for_slug = final_meta.get("title", "")
# Prio 2: Body Snippet (wenn Titel immer noch leer)
if not title_for_slug:
clean_body = re.sub(r"[#*_\[\]()]", "", final_body).strip()
title_for_slug = clean_body[:40] if clean_body else "draft"
safe_title = slugify(title_for_slug)[:60]
if not safe_title: safe_title = "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)
if "error" in result: if "error" in result: