From 1063c94f5dbf38bd6b18dc5e0ba5a030b1558406 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 10 Dec 2025 16:41:21 +0100 Subject: [PATCH] =?UTF-8?q?UI=20Anpasung=20f=C3=BCr=20WP07?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/frontend/ui.py | 92 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 18 deletions(-) diff --git a/app/frontend/ui.py b/app/frontend/ui.py index 418e82c..f22ea22 100644 --- a/app/frontend/ui.py +++ b/app/frontend/ui.py @@ -3,6 +3,7 @@ import requests import uuid import os import json +import re from pathlib import Path from dotenv import load_dotenv @@ -18,7 +19,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.1", page_icon="🧠", layout="wide") +st.set_page_config(page_title="mindnet v2.3.2", page_icon="🧠", layout="wide") # --- CSS STYLING --- st.markdown(""" @@ -40,6 +41,9 @@ st.markdown(""" /* Expander Cleaner */ .streamlit-expanderHeader { font-size: 0.9rem; font-weight: 600; color: #444; } + + /* Editor Label */ + .editor-label { font-weight: bold; margin-bottom: 5px; display: block; color: #333; } """, unsafe_allow_html=True) @@ -50,6 +54,14 @@ if "draft_note" not in st.session_state: st.session_state.draft_note = {"title": # --- HELPER FUNCTIONS --- +def extract_markdown_content(text): + """Extrahiert den Inhalt aus einem Markdown-Codeblock.""" + pattern = r"```markdown\s*(.*?)\s*```" + match = re.search(pattern, text, re.DOTALL) + if match: + return match.group(1).strip() + return text # Fallback: Ganzen Text zurückgeben, wenn kein Block gefunden + def load_history_from_logs(limit=10): """Liest die letzten N Queries aus dem Logfile.""" queries = [] @@ -93,9 +105,9 @@ def submit_feedback(query_id, node_id, score, comment=None): def render_sidebar(): with st.sidebar: st.title("🧠 mindnet") - st.caption("v2.3.1 | WP-10 UI") + st.caption("v2.3.2 | WP-10 UI") - mode = st.radio("Modus", ["💬 Chat", "📝 Neuer Eintrag (WP-07)"], index=0) + mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Eintrag"], index=0) st.divider() st.subheader("⚙️ Settings") @@ -119,17 +131,59 @@ def render_chat_interface(top_k, explain): for msg in st.session_state.messages: with st.chat_message(msg["role"]): if msg["role"] == "assistant": - # Intent Badge MIT SOURCE (Fix für Debugging) + # 1. INTENT BADGE (mit Source) if "intent" in msg: - icon = {"EMPATHY": "❤️", "DECISION": "⚖️", "CODING": "💻", "FACT": "📚"}.get(msg["intent"], "🧠") + intent = msg["intent"] + icon = {"EMPATHY": "❤️", "DECISION": "⚖️", "CODING": "💻", "FACT": "📚", "INTERVIEW": "📝"}.get(intent, "🧠") source_info = msg.get("intent_source", "Unknown") - # Hier wird die Quelle wieder angezeigt: - st.markdown(f'
{icon} Intent: {msg["intent"]} via {source_info}
', unsafe_allow_html=True) + st.markdown(f'
{icon} Intent: {intent} via {source_info}
', unsafe_allow_html=True) - st.markdown(msg["content"]) + # 2. CONTENT RENDERING (Weiche für INTERVIEW vs. NORMAL) + if msg.get("intent") == "INTERVIEW": + # --- INTERVIEW EDITOR MODUS --- + + # Markdown extrahieren (nur beim ersten Mal, dann aus State) + raw_content = msg["content"] + draft_content = extract_markdown_content(raw_content) + + # Eindeutiger Key für diesen Editor (basierend auf query_id) + editor_key = f"editor_{msg.get('query_id', uuid.uuid4())}" + + # Init State falls noch nicht vorhanden + if editor_key not in st.session_state: + st.session_state[editor_key] = draft_content + + st.markdown('Entwurf bearbeiten (Draft):', unsafe_allow_html=True) + + # Der Editor + edited_text = st.text_area( + label="Editor", + value=st.session_state[editor_key], + height=350, + key=editor_key, + label_visibility="collapsed" + ) + + # Action Buttons + c1, c2 = st.columns([1, 1]) + with c1: + st.download_button( + label="💾 Als .md herunterladen", + data=edited_text, + file_name=f"draft_{msg.get('query_id', 'unknown')[:8]}.md", + mime="text/markdown" + ) + with c2: + # Copy Mockup (Browser-Security verhindert oft direkten Zugriff) + if st.button("📋 In Zwischenablage kopieren", key=f"copy_{editor_key}"): + st.toast("Tipp: Klicke in das Textfeld, drücke Strg+A dann Strg+C.") + + else: + # --- STANDARD CHAT MODUS --- + st.markdown(msg["content"]) - # Sources - if "sources" in msg: + # 3. SOURCES (Nur anzeigen, wenn vorhanden) + if "sources" in msg and msg["sources"]: for hit in msg["sources"]: score = hit.get('total_score', 0) icon = "🟢" if score > 0.8 else "🟡" if score > 0.5 else "⚪" @@ -138,24 +192,26 @@ def render_chat_interface(top_k, explain): if hit.get('explanation'): st.caption(f"Grund: {hit['explanation']['reasons'][0]['message']}") - # Granular Feedback (Faces) + # Granular Feedback def _cb(qid=msg["query_id"], nid=hit['node_id']): val = st.session_state.get(f"fb_src_{qid}_{nid}") if val is not None: submit_feedback(qid, nid, val+1, "Faces UI") st.feedback("faces", key=f"fb_src_{msg['query_id']}_{hit['node_id']}", on_change=_cb) - # Global Feedback (Stars) - 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)) + # 4. GLOBAL FEEDBACK + 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)) else: + # User Message st.markdown(msg["content"]) # Input Logic last_msg_is_user = len(st.session_state.messages) > 0 and st.session_state.messages[-1]["role"] == "user" - if prompt := st.chat_input("Frage Mindnet..."): + if prompt := st.chat_input("Frage Mindnet... (z.B. 'Neues Projekt anlegen')"): st.session_state.messages.append({"role": "user", "content": prompt}) st.rerun() @@ -172,7 +228,7 @@ def render_chat_interface(top_k, explain): "role": "assistant", "content": resp.get("answer"), "intent": resp.get("intent", "FACT"), - "intent_source": resp.get("intent_source", "Unknown"), # Wichtig für Anzeige + "intent_source": resp.get("intent_source", "Unknown"), "sources": resp.get("sources", []), "query_id": resp.get("query_id") } @@ -180,8 +236,8 @@ def render_chat_interface(top_k, explain): st.rerun() def render_creation_interface(): - st.header("📝 Neuer Wissens-Eintrag (WP-07/11)") - st.info("Hier kannst du strukturierte Notizen erstellen, die direkt in den Obsidian Vault gespeichert werden.") + st.header("📝 Manueller Eintrag (Legacy)") + st.info("Nutze lieber den Chat ('Neues Projekt anlegen') für den Interview-Modus.") with st.form("new_entry"): col1, col2 = st.columns([3, 1])