diff --git a/app/frontend/ui.py b/app/frontend/ui.py
index b3f52a8..0fa5783 100644
--- a/app/frontend/ui.py
+++ b/app/frontend/ui.py
@@ -21,7 +21,7 @@ timeout_setting = os.getenv("MINDNET_API_TIMEOUT") or os.getenv("MINDNET_LLM_TIM
API_TIMEOUT = float(timeout_setting) if timeout_setting else 300.0
# --- PAGE SETUP ---
-st.set_page_config(page_title="mindnet v2.3.2", page_icon="๐ง ", layout="wide")
+st.set_page_config(page_title="mindnet v2.3.2 (Debug Mode)", page_icon="๐", layout="wide")
# --- CSS STYLING ---
st.markdown("""
@@ -35,7 +35,6 @@ st.markdown("""
border: 1px solid #d2e3fc; display: inline-block; margin-bottom: 0.5rem;
}
- /* Editor Box Styling */
.draft-box {
border: 1px solid #d0d7de;
border-radius: 6px;
@@ -45,7 +44,6 @@ st.markdown("""
margin-bottom: 10px;
}
- /* Preview Styling mimics GitHub Markdown */
.preview-box {
border: 1px solid #e0e0e0;
border-radius: 6px;
@@ -60,54 +58,57 @@ st.markdown("""
if "messages" not in st.session_state: st.session_state.messages = []
if "user_id" not in st.session_state: st.session_state.user_id = str(uuid.uuid4())
-# --- HELPER FUNCTIONS ---
+# --- HELPER FUNCTIONS (ROBUST PARSING) ---
def parse_markdown_draft(full_text):
"""
- Zerlegt die LLM-Antwort in Frontmatter (Dict) und Body (String).
- Robust gegen Einleitungstexte vor dem Codeblock.
+ Versucht extrem tolerant, Frontmatter und Body zu trennen.
"""
- # 1. Extrahiere Codeblock
- pattern_block = r"```markdown\s*(.*?)\s*```"
- match_block = re.search(pattern_block, full_text, re.DOTALL)
+ clean_text = full_text
- # Fallback: Wenn kein Codeblock, nimm ganzen Text
- clean_text = match_block.group(1).strip() if match_block else full_text
+ # 1. Versuch: Markdown Fences entfernen (egal ob ```markdown, ```md oder nur ```)
+ # re.IGNORECASE und re.DOTALL sind wichtig!
+ pattern_block = r"```(?:markdown|md)?\s*(.*?)\s*```"
+ match_block = re.search(pattern_block, full_text, re.DOTALL | re.IGNORECASE)
+
+ if match_block:
+ clean_text = match_block.group(1).strip()
+ # Debugging Info im UI anzeigen ist schwer hier, wir verlassen uns auf den Return
- # 2. Trenne Frontmatter (YAML) vom Body
- # Sucht nach --- am Anfang, gefolgt von YAML, gefolgt von ---
- pattern_fm = r"^---\s+(.*?)\s+---\s*(.*)$"
+ # 2. Versuch: YAML Frontmatter finden
+ # Suche nach --- am Anfang (oder nach Whitespace), gefolgt von Inhalt, gefolgt von ---
+ pattern_fm = r"^\s*---\s+(.*?)\s+---\s*(.*)$"
match_fm = re.search(pattern_fm, clean_text, re.DOTALL)
+ meta = {}
+ body = clean_text
+
if match_fm:
yaml_str = match_fm.group(1)
body = match_fm.group(2)
try:
- meta = yaml.safe_load(yaml_str) or {}
- except Exception:
- meta = {} # Fallback bei kaputtem YAML
- return meta, body
- else:
- # Kein Frontmatter gefunden -> Alles ist Body
- return {}, clean_text
-
-def generate_filename(meta):
- """Generiert einen Dateinamen basierend auf Typ und Datum."""
- date_str = datetime.now().strftime("%Y%m%d")
- n_type = meta.get("type", "note")
- # Versuche Titel aus Body zu raten wรคre zu komplex, wir nehmen Generic
- return f"{date_str}-{n_type}-draft.md"
+ # YAML laden, aber Fehler abfangen
+ parsed = yaml.safe_load(yaml_str)
+ if isinstance(parsed, dict):
+ meta = parsed
+ except Exception as e:
+ print(f"YAML Parsing Error: {e}") # Geht in Server Log
+ # Wir behalten body, meta bleibt leer
+
+ return meta, body
def build_markdown_doc(meta, body):
"""Baut das finale Dokument zusammen."""
- # ID Generierung beim 'Speichern' (hier simuliert fรผr Download)
if "id" not in meta or meta["id"] == "generated_on_save":
meta["id"] = f"{datetime.now().strftime('%Y%m%d')}-{meta.get('type', 'note')}-{uuid.uuid4().hex[:6]}"
- # Updated timestamp
meta["updated"] = datetime.now().strftime("%Y-%m-%d")
- yaml_str = yaml.dump(meta, default_flow_style=None, sort_keys=False, allow_unicode=True).strip()
+ try:
+ yaml_str = yaml.dump(meta, default_flow_style=None, sort_keys=False, allow_unicode=True).strip()
+ except:
+ yaml_str = "error: generating_yaml"
+
return f"---\n{yaml_str}\n---\n\n{body}"
def load_history_from_logs(limit=10):
@@ -151,7 +152,7 @@ def submit_feedback(query_id, node_id, score, comment=None):
def render_sidebar():
with st.sidebar:
st.title("๐ง mindnet")
- st.caption("v2.3.2 | WP-10 UI")
+ st.caption("DEBUG MODE | WP-10 UI")
mode = st.radio("Modus", ["๐ฌ Chat", "๐ Manueller Editor"], index=0)
@@ -176,8 +177,11 @@ def render_draft_editor(msg):
key_base = f"draft_{qid}"
# 1. State Initialisierung (Nur einmal pro Nachricht)
+ # Wir parsen IMMER neu, wenn der Key noch nicht im State ist
if f"{key_base}_init" not in st.session_state:
meta, body = parse_markdown_draft(msg["content"])
+
+ # Fallback Defaults
st.session_state[f"{key_base}_type"] = meta.get("type", "default")
# Tags Behandlung (Liste oder String)
@@ -193,39 +197,36 @@ def render_draft_editor(msg):
# 2. Editor Container
st.markdown(f'
', unsafe_allow_html=True)
st.markdown("### ๐ Entwurf bearbeiten")
- st.caption("Das System hat diesen Entwurf vorbereitet. Ergรคnze die fehlenden [TODO] Bereiche.")
-
+
# 3. Metadaten (Grid Layout)
c1, c2 = st.columns([1, 2])
with c1:
- # Typen mรผssen mit types.yaml synchron sein
- known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle"]
- curr_type = st.session_state[f"{key_base}_type"]
+ known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle", "default"]
+ curr_type = st.session_state.get(f"{key_base}_type", "default")
if curr_type not in known_types: known_types.append(curr_type)
+ # Selectbox mit State-Sync
new_type = st.selectbox("Typ", known_types, index=known_types.index(curr_type), key=f"{key_base}_sel_type")
with c2:
- new_tags = st.text_input("Tags (kommagetrennt)", value=st.session_state[f"{key_base}_tags"], key=f"{key_base}_inp_tags")
+ new_tags = st.text_input("Tags (kommagetrennt)", value=st.session_state.get(f"{key_base}_tags", ""), key=f"{key_base}_inp_tags")
# 4. Inhalt (Tabs)
tab_edit, tab_view = st.tabs(["โ๏ธ Editor", "๐๏ธ Vorschau"])
with tab_edit:
- # Hier landet der Body mit den ## Headings und [TODO]s
new_body = st.text_area(
- "Inhalt",
- value=st.session_state[f"{key_base}_body"],
+ "Inhalt (Markdown Body)",
+ value=st.session_state.get(f"{key_base}_body", ""),
height=500,
key=f"{key_base}_txt_body",
- label_visibility="collapsed",
- help="Hier kannst du Markdown schreiben."
+ label_visibility="collapsed"
)
- # Live-Zusammenbau fรผr Vorschau & Download
+ # Live-Zusammenbau
final_tags_list = [t.strip() for t in new_tags.split(",") if t.strip()]
final_meta = {
- "id": "generated_on_save", # Placeholder, wird beim Download ersetzt
+ "id": "generated_on_save",
"type": new_type,
"status": "draft",
"tags": final_tags_list
@@ -234,47 +235,48 @@ def render_draft_editor(msg):
with tab_view:
st.markdown('
', unsafe_allow_html=True)
- st.markdown(final_doc) # Rendered Markdown
+ st.markdown(final_doc)
st.markdown('
', unsafe_allow_html=True)
st.markdown("---")
- # 5. Actions (Writeback Simulation)
+ # 5. Actions
b1, b2 = st.columns([1, 1])
with b1:
- # Download Button (Client-Side)
st.download_button(
- label="๐พ Als .md Datei speichern",
+ label="๐พ Download .md",
data=final_doc,
file_name=generate_filename(final_meta),
mime="text/markdown"
)
with b2:
- # Copy Button Logic
- if st.button("๐ Code anzeigen (Copy)", key=f"{key_base}_btn_copy"):
+ if st.button("๐ Code Copy", key=f"{key_base}_btn_copy"):
st.code(final_doc, language="markdown")
st.markdown("
", unsafe_allow_html=True)
def render_chat_interface(top_k, explain):
- for msg in st.session_state.messages:
+ for idx, msg in enumerate(st.session_state.messages):
with st.chat_message(msg["role"]):
if msg["role"] == "assistant":
# Intent Badge
- if "intent" in msg:
- intent = msg["intent"]
- icon = {"EMPATHY": "โค๏ธ", "DECISION": "โ๏ธ", "CODING": "๐ป", "FACT": "๐", "INTERVIEW": "๐"}.get(intent, "๐ง ")
- src = msg.get("intent_source", "?")
- st.markdown(f'{icon} Intent: {intent} ({src})
', unsafe_allow_html=True)
+ intent = msg.get("intent", "UNKNOWN")
+ src = msg.get("intent_source", "?")
- # INTERVIEW Logic vs NORMAL Chat
- if msg.get("intent") == "INTERVIEW":
+ # Icon Mapping
+ icon_map = {"EMPATHY":"โค๏ธ", "DECISION":"โ๏ธ", "CODING":"๐ป", "FACT":"๐", "INTERVIEW":"๐"}
+ icon = icon_map.get(intent, "๐ง ")
+
+ st.markdown(f'{icon} Intent: {intent} ({src})
', unsafe_allow_html=True)
+
+ # --- LOGIC SWITCH ---
+ if intent == "INTERVIEW":
render_draft_editor(msg)
else:
st.markdown(msg["content"])
- # Sources & Feedback (nur bei RAG sinnvoll)
+ # --- SOURCES ---
if "sources" in msg and msg["sources"]:
for hit in msg["sources"]:
score = hit.get('total_score', 0)
@@ -284,18 +286,20 @@ def render_chat_interface(top_k, explain):
if hit.get('explanation'):
st.caption(f"Grund: {hit['explanation']['reasons'][0]['message']}")
- def _cb(qid=msg["query_id"], nid=hit['node_id']):
+ def _cb(qid=msg.get("query_id"), nid=hit.get('node_id')):
val = st.session_state.get(f"fb_src_{qid}_{nid}")
if val is not None: submit_feedback(qid, nid, val+1)
- st.feedback("faces", key=f"fb_src_{msg['query_id']}_{hit['node_id']}", on_change=_cb)
+
+ st.feedback("faces", key=f"fb_src_{msg.get('query_id')}_{hit.get('node_id')}", on_change=_cb)
- if "query_id" in msg:
- qid = msg["query_id"]
- st.feedback("stars", key=f"fb_glob_{qid}", on_change=lambda: submit_feedback(qid, "generated_answer", st.session_state[f"fb_glob_{qid}"]+1))
+ # --- DEBUGGING (NEU) ---
+ with st.expander("๐ Debug Raw Payload"):
+ st.json(msg) # Zeigt exakt, was das Backend geschickt hat
+
else:
st.markdown(msg["content"])
- # Input Loop
+ # Input
if prompt := st.chat_input("Frage Mindnet... (z.B. 'Neues Projekt anlegen')"):
st.session_state.messages.append({"role": "user", "content": prompt})
st.rerun()
@@ -322,8 +326,6 @@ def render_chat_interface(top_k, explain):
def render_manual_editor():
st.header("๐ Manueller Editor")
st.info("Hier kannst du eine Notiz komplett von Null erstellen.")
-
- # Wiederverwendung der Editor-Logik wรคre ideal, hier vereinfacht:
c1, c2 = st.columns([1, 2])
n_type = c1.selectbox("Typ", ["concept", "project", "decision", "experience", "value", "goal"])
tags = c2.text_input("Tags")