213 lines
9.3 KiB
Python
213 lines
9.3 KiB
Python
import streamlit as st
|
||
import uuid
|
||
import re
|
||
from datetime import datetime
|
||
|
||
from ui_utils import parse_markdown_draft, build_markdown_doc, slugify
|
||
from ui_api import save_draft_to_vault, analyze_draft_text
|
||
|
||
def render_draft_editor(msg):
|
||
"""
|
||
Rendert den Markdown-Editor.
|
||
Nutzt 'origin_filename' aus der Message, um zwischen Update und Neu zu unterscheiden.
|
||
"""
|
||
if "query_id" not in msg or not msg["query_id"]:
|
||
msg["query_id"] = str(uuid.uuid4())
|
||
|
||
qid = msg["query_id"]
|
||
key_base = f"draft_{qid}"
|
||
|
||
# State Keys
|
||
data_meta_key = f"{key_base}_data_meta"
|
||
data_sugg_key = f"{key_base}_data_suggestions"
|
||
widget_body_key = f"{key_base}_widget_body"
|
||
data_body_key = f"{key_base}_data_body"
|
||
|
||
# --- INIT STATE ---
|
||
if f"{key_base}_init" not in st.session_state:
|
||
meta, body = parse_markdown_draft(msg["content"])
|
||
if "type" not in meta: meta["type"] = "default"
|
||
if "title" not in meta: meta["title"] = ""
|
||
tags = meta.get("tags", [])
|
||
meta["tags_str"] = ", ".join(tags) if isinstance(tags, list) else str(tags)
|
||
|
||
st.session_state[data_meta_key] = meta
|
||
st.session_state[data_sugg_key] = []
|
||
st.session_state[data_body_key] = body.strip()
|
||
|
||
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"]
|
||
|
||
# Pfad übernehmen (Source of Truth)
|
||
st.session_state[f"{key_base}_origin_filename"] = msg.get("origin_filename")
|
||
st.session_state[f"{key_base}_init"] = True
|
||
|
||
# --- RESURRECTION ---
|
||
if widget_body_key not in st.session_state and data_body_key in st.session_state:
|
||
st.session_state[widget_body_key] = st.session_state[data_body_key]
|
||
|
||
# --- SYNC HELPER ---
|
||
def _sync_meta():
|
||
meta = st.session_state[data_meta_key]
|
||
meta["title"] = st.session_state.get(f"{key_base}_wdg_title", "")
|
||
meta["type"] = st.session_state.get(f"{key_base}_wdg_type", "default")
|
||
meta["tags_str"] = st.session_state.get(f"{key_base}_wdg_tags", "")
|
||
st.session_state[data_meta_key] = meta
|
||
|
||
def _sync_body():
|
||
st.session_state[data_body_key] = st.session_state[widget_body_key]
|
||
|
||
def _insert_text(t):
|
||
st.session_state[widget_body_key] = f"{st.session_state.get(widget_body_key, '')}\n\n{t}"
|
||
st.session_state[data_body_key] = st.session_state[widget_body_key]
|
||
|
||
def _remove_text(t):
|
||
st.session_state[widget_body_key] = st.session_state.get(widget_body_key, '').replace(t, "").strip()
|
||
st.session_state[data_body_key] = st.session_state[widget_body_key]
|
||
|
||
# --- UI LAYOUT ---
|
||
|
||
# Header Info (Debug Pfad anzeigen, damit wir sicher sind)
|
||
origin_fname = st.session_state.get(f"{key_base}_origin_filename")
|
||
|
||
if origin_fname:
|
||
# Dateiname extrahieren für saubere Anzeige
|
||
display_name = str(origin_fname).split("/")[-1]
|
||
st.success(f"📂 **Update-Modus**: `{display_name}`")
|
||
# Debugging: Zeige vollen Pfad im Tooltip oder klein darunter
|
||
with st.expander("Pfad-Details", expanded=False):
|
||
st.code(origin_fname)
|
||
st.markdown(f'<div class="draft-box" style="border-left: 5px solid #ff9f43;">', unsafe_allow_html=True)
|
||
else:
|
||
st.info("✨ **Erstell-Modus**: Neue Datei wird angelegt.")
|
||
st.markdown(f'<div class="draft-box">', unsafe_allow_html=True)
|
||
|
||
st.markdown("### Editor")
|
||
|
||
# Meta Felder
|
||
meta_ref = st.session_state[data_meta_key]
|
||
c1, c2 = st.columns([2, 1])
|
||
with c1:
|
||
st.text_input("Titel", key=f"{key_base}_wdg_title", on_change=_sync_meta)
|
||
with c2:
|
||
known_types = ["concept", "project", "decision", "experience", "journal", "value", "goal", "principle", "risk", "belief"]
|
||
curr_type = st.session_state.get(f"{key_base}_wdg_type", meta_ref["type"])
|
||
if curr_type not in known_types: known_types.append(curr_type)
|
||
st.selectbox("Typ", known_types, key=f"{key_base}_wdg_type", on_change=_sync_meta)
|
||
|
||
st.text_input("Tags", key=f"{key_base}_wdg_tags", on_change=_sync_meta)
|
||
|
||
# Tabs
|
||
tab_edit, tab_intel, tab_view = st.tabs(["✏️ Inhalt", "🧠 Intelligence", "👁️ Vorschau"])
|
||
|
||
with tab_edit:
|
||
st.text_area("Body", key=widget_body_key, height=600, on_change=_sync_body, label_visibility="collapsed")
|
||
|
||
with tab_intel:
|
||
st.info("Analysiert den Text auf Verknüpfungsmöglichkeiten.")
|
||
if st.button("🔍 Analyse starten", key=f"{key_base}_analyze"):
|
||
st.session_state[data_sugg_key] = []
|
||
text_to_analyze = st.session_state.get(widget_body_key, st.session_state.get(data_body_key, ""))
|
||
with st.spinner("Analysiere..."):
|
||
analysis = analyze_draft_text(text_to_analyze, st.session_state.get(f"{key_base}_wdg_type", "concept"))
|
||
if "error" in analysis:
|
||
st.error(f"Fehler: {analysis['error']}")
|
||
else:
|
||
suggestions = analysis.get("suggestions", [])
|
||
st.session_state[data_sugg_key] = suggestions
|
||
if not suggestions: st.warning("Keine Vorschläge.")
|
||
else: st.success(f"{len(suggestions)} Vorschläge gefunden.")
|
||
|
||
suggestions = st.session_state[data_sugg_key]
|
||
if suggestions:
|
||
current_text = st.session_state.get(widget_body_key, "")
|
||
for idx, sugg in enumerate(suggestions):
|
||
link_text = sugg.get('suggested_markdown', '')
|
||
is_inserted = link_text in current_text
|
||
bg_color = "#e6fffa" if is_inserted else "#ffffff"
|
||
border = "3px solid #28a745" if is_inserted else "3px solid #1a73e8"
|
||
st.markdown(f"<div style='border-left: {border}; background-color: {bg_color}; padding: 10px; margin-bottom: 8px;'><b>{sugg.get('target_title')}</b> <small>({sugg.get('type')})</small><br><i>{sugg.get('reason')}</i><br><code>{link_text}</code></div>", unsafe_allow_html=True)
|
||
if is_inserted:
|
||
st.button("❌ Entfernen", key=f"del_{idx}_{key_base}", on_click=_remove_text, args=(link_text,))
|
||
else:
|
||
st.button("➕ Einfügen", key=f"add_{idx}_{key_base}", on_click=_insert_text, args=(link_text,))
|
||
|
||
# Save Logic Preparation
|
||
final_tags = [t.strip() for t in st.session_state.get(f"{key_base}_wdg_tags", "").split(",") if t.strip()]
|
||
final_meta = {
|
||
"id": "generated_on_save",
|
||
"type": st.session_state.get(f"{key_base}_wdg_type", "default"),
|
||
"title": st.session_state.get(f"{key_base}_wdg_title", "").strip(),
|
||
"status": "draft",
|
||
"tags": final_tags
|
||
}
|
||
if "origin_note_id" in msg:
|
||
final_meta["id"] = msg["origin_note_id"]
|
||
|
||
final_body = st.session_state.get(widget_body_key, st.session_state[data_body_key])
|
||
if not final_meta["title"]:
|
||
h1_match = re.search(r"^#\s+(.*)$", final_body, re.MULTILINE)
|
||
if h1_match: final_meta["title"] = h1_match.group(1).strip()
|
||
|
||
final_doc = build_markdown_doc(final_meta, final_body)
|
||
|
||
with tab_view:
|
||
st.markdown('<div class="preview-box">', unsafe_allow_html=True)
|
||
st.markdown(final_doc)
|
||
st.markdown('</div>', unsafe_allow_html=True)
|
||
|
||
st.markdown("---")
|
||
|
||
# Save Actions
|
||
b1, b2 = st.columns([1, 1])
|
||
with b1:
|
||
save_label = "💾 Update speichern" if origin_fname else "💾 Neu anlegen & Indizieren"
|
||
|
||
if st.button(save_label, type="primary", key=f"{key_base}_save"):
|
||
with st.spinner("Speichere im Vault..."):
|
||
if origin_fname:
|
||
# UPDATE: Ziel ist der exakte Pfad
|
||
target_file = origin_fname
|
||
else:
|
||
# CREATE: Neuer Dateiname
|
||
raw_title = final_meta.get("title", "draft")
|
||
target_file = f"{datetime.now().strftime('%Y%m%d')}-{slugify(raw_title)[:60]}.md"
|
||
|
||
result = save_draft_to_vault(final_doc, filename=target_file)
|
||
if "error" in result:
|
||
st.error(f"Fehler: {result['error']}")
|
||
else:
|
||
st.success(f"Gespeichert: {result.get('file_path')}")
|
||
st.balloons()
|
||
with b2:
|
||
if st.button("📋 Code anzeigen", key=f"{key_base}_btn_copy"):
|
||
st.code(final_doc, language="markdown")
|
||
|
||
st.markdown("</div>", unsafe_allow_html=True)
|
||
|
||
def render_manual_editor():
|
||
"""
|
||
Rendert den manuellen Editor.
|
||
PRÜFT, ob eine Edit-Anfrage aus dem Graphen vorliegt!
|
||
"""
|
||
|
||
target_msg = None
|
||
|
||
# 1. Prüfen: Gibt es Nachrichten im Verlauf?
|
||
if st.session_state.messages:
|
||
last_msg = st.session_state.messages[-1]
|
||
|
||
# 2. Ist die letzte Nachricht eine Edit-Anfrage? (Erkennbar am query_id prefix 'edit_')
|
||
qid = str(last_msg.get("query_id", ""))
|
||
if qid.startswith("edit_"):
|
||
target_msg = last_msg
|
||
|
||
# 3. Fallback: Leeres Template, falls keine Edit-Anfrage vorliegt
|
||
if not target_msg:
|
||
target_msg = {
|
||
"content": "---\ntype: concept\ntitle: Neue Notiz\nstatus: draft\ntags: []\n---\n# Titel\n",
|
||
"query_id": f"manual_{uuid.uuid4()}" # Eigene ID, damit neuer State entsteht
|
||
}
|
||
|
||
render_draft_editor(target_msg) |