WP11 #8
|
|
@ -240,80 +240,67 @@ def render_draft_editor(msg):
|
||||||
qid = msg.get('query_id', str(uuid.uuid4()))
|
qid = msg.get('query_id', str(uuid.uuid4()))
|
||||||
key_base = f"draft_{qid}"
|
key_base = f"draft_{qid}"
|
||||||
|
|
||||||
# === STATE MANAGEMENT KEYS ===
|
# State Keys
|
||||||
# Wir nutzen getrennte Keys für Widget und Daten, um Streamlit's Bereinigung zu umgehen.
|
data_body_key = f"{key_base}_data_body" # Persistenter Speicher
|
||||||
# Persistent Keys (bleiben erhalten auch beim Tab-Wechsel)
|
data_meta_key = f"{key_base}_data_meta" # Metadaten
|
||||||
data_body_key = f"{key_base}_data_body"
|
data_sugg_key = f"{key_base}_data_suggestions" # Vorschläge
|
||||||
data_meta_key = f"{key_base}_data_meta"
|
widget_body_key = f"{key_base}_widget_body" # Das Textfeld selbst
|
||||||
data_sugg_key = f"{key_base}_data_suggestions"
|
|
||||||
|
|
||||||
# Widget Keys (können sich ändern/neu gezeichnet werden)
|
# --- 1. INIT STATE ---
|
||||||
widget_body_key = f"{key_base}_widget_body"
|
|
||||||
|
|
||||||
# --- 1. INIT STATE (Einmalig pro Nachricht) ---
|
|
||||||
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"])
|
||||||
|
|
||||||
# Defaults setzen
|
# Metadata Defaults
|
||||||
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"] = ""
|
||||||
if "tags" not in meta: meta["tags"] = []
|
tags = meta.get("tags", [])
|
||||||
|
meta["tags_str"] = ", ".join(tags) if isinstance(tags, list) else str(tags)
|
||||||
|
|
||||||
# Tags Listen-Check
|
# Init Session State
|
||||||
if isinstance(meta["tags"], list):
|
|
||||||
meta["tags_str"] = ", ".join(meta["tags"])
|
|
||||||
else:
|
|
||||||
meta["tags_str"] = str(meta.get("tags", ""))
|
|
||||||
|
|
||||||
# Persistent speichern
|
|
||||||
st.session_state[data_meta_key] = meta
|
st.session_state[data_meta_key] = meta
|
||||||
st.session_state[data_body_key] = body.strip()
|
st.session_state[data_body_key] = body.strip()
|
||||||
st.session_state[data_sugg_key] = []
|
st.session_state[data_sugg_key] = []
|
||||||
st.session_state[f"{key_base}_init"] = True
|
st.session_state[f"{key_base}_init"] = True
|
||||||
|
|
||||||
# --- HELPER CALLBACKS ---
|
# --- CALLBACKS ---
|
||||||
# Sync Widget -> Data
|
|
||||||
def _sync_body():
|
def _sync_body():
|
||||||
|
# Schreibt Widget-Inhalt in den Speicher
|
||||||
st.session_state[data_body_key] = st.session_state[widget_body_key]
|
st.session_state[data_body_key] = st.session_state[widget_body_key]
|
||||||
|
|
||||||
# Insert Text (Daten ändern)
|
|
||||||
def _insert_text(text_to_insert):
|
def _insert_text(text_to_insert):
|
||||||
current = st.session_state[data_body_key]
|
# Liest vom Widget (aktuellster Stand!), fügt an, schreibt zurück
|
||||||
|
current = st.session_state[widget_body_key]
|
||||||
st.session_state[data_body_key] = f"{current}\n\n{text_to_insert}"
|
st.session_state[data_body_key] = f"{current}\n\n{text_to_insert}"
|
||||||
|
# Wichtig: Leere Vorschläge nach Insert, damit man nicht doppelt klickt
|
||||||
|
# st.session_state[data_sugg_key] = []
|
||||||
|
|
||||||
def _remove_text(text_to_remove):
|
def _remove_text(text_to_remove):
|
||||||
current = st.session_state[data_body_key]
|
current = st.session_state[widget_body_key]
|
||||||
st.session_state[data_body_key] = current.replace(text_to_remove, "").strip()
|
st.session_state[data_body_key] = current.replace(text_to_remove, "").strip()
|
||||||
|
|
||||||
# --- 2. UI LAYOUT ---
|
# --- UI LAYOUT ---
|
||||||
st.markdown(f'<div class="draft-box">', unsafe_allow_html=True)
|
st.markdown(f'<div class="draft-box">', unsafe_allow_html=True)
|
||||||
st.markdown("### 📝 Entwurf bearbeiten")
|
st.markdown("### 📝 Entwurf bearbeiten")
|
||||||
|
|
||||||
# Load Data Reference
|
|
||||||
meta_ref = st.session_state[data_meta_key]
|
|
||||||
|
|
||||||
# Metadata Form
|
# Metadata Form
|
||||||
|
meta_ref = st.session_state[data_meta_key]
|
||||||
c1, c2 = st.columns([2, 1])
|
c1, c2 = st.columns([2, 1])
|
||||||
with c1:
|
with c1:
|
||||||
new_title = st.text_input("Titel", key=f"{key_base}_wdg_title", value=meta_ref["title"])
|
meta_ref["title"] = st.text_input("Titel", key=f"{key_base}_wdg_title", value=meta_ref["title"])
|
||||||
meta_ref["title"] = new_title # Direct Sync
|
|
||||||
with c2:
|
with c2:
|
||||||
known_types = ["concept", "project", "decision", "experience", "journal", "person", "value", "goal", "principle", "default"]
|
known_types = ["concept", "project", "decision", "experience", "journal", "value", "goal", "principle"]
|
||||||
curr_type = meta_ref["type"]
|
curr = meta_ref["type"]
|
||||||
if curr_type not in known_types: known_types.append(curr_type)
|
if curr not in known_types: known_types.append(curr)
|
||||||
new_type = st.selectbox("Typ", known_types, index=known_types.index(curr_type), key=f"{key_base}_wdg_type")
|
meta_ref["type"] = st.selectbox("Typ", known_types, index=known_types.index(curr), key=f"{key_base}_wdg_type")
|
||||||
meta_ref["type"] = new_type # Direct Sync
|
|
||||||
|
|
||||||
new_tags = st.text_input("Tags", key=f"{key_base}_wdg_tags", value=meta_ref.get("tags_str", ""))
|
meta_ref["tags_str"] = st.text_input("Tags", key=f"{key_base}_wdg_tags", value=meta_ref.get("tags_str", ""))
|
||||||
meta_ref["tags_str"] = new_tags # Direct Sync
|
|
||||||
|
|
||||||
# Tabs
|
# Tabs
|
||||||
tab_edit, tab_intel, tab_view = st.tabs(["✏️ Inhalt", "🧠 Intelligence", "👁️ Vorschau"])
|
tab_edit, tab_intel, tab_view = st.tabs(["✏️ Inhalt", "🧠 Intelligence", "👁️ Vorschau"])
|
||||||
|
|
||||||
# --- TAB 1: EDITOR ---
|
# --- TAB 1: EDITOR ---
|
||||||
with tab_edit:
|
with tab_edit:
|
||||||
# Hier ist der Trick: Value kommt aus 'data_body_key',
|
# Value kommt aus Data, Änderungen gehen via Callback zurück in Data
|
||||||
# Änderungen triggern '_sync_body', der zurück in 'data_body_key' schreibt.
|
|
||||||
st.text_area(
|
st.text_area(
|
||||||
"Body",
|
"Body",
|
||||||
key=widget_body_key,
|
key=widget_body_key,
|
||||||
|
|
@ -328,12 +315,18 @@ def render_draft_editor(msg):
|
||||||
st.info("Klicke auf 'Analysieren', um Verknüpfungen für den AKTUELLEN Text zu finden.")
|
st.info("Klicke auf 'Analysieren', um Verknüpfungen für den AKTUELLEN Text zu finden.")
|
||||||
|
|
||||||
if st.button("🔍 Analyse starten", key=f"{key_base}_analyze"):
|
if st.button("🔍 Analyse starten", key=f"{key_base}_analyze"):
|
||||||
# 1. Alte Ergebnisse löschen für Feedback
|
# 1. Reset
|
||||||
st.session_state[data_sugg_key] = []
|
st.session_state[data_sugg_key] = []
|
||||||
|
|
||||||
|
# 2. Text holen (HIER WAR DER FEHLER)
|
||||||
|
# Wir holen ihn direkt aus dem Widget-Key, da dieser immer aktuell ist,
|
||||||
|
# auch wenn on_change noch nicht gefeuert hat.
|
||||||
|
text_to_analyze = st.session_state.get(widget_body_key, st.session_state[data_body_key])
|
||||||
|
|
||||||
|
# Debug (optional, damit du siehst was passiert)
|
||||||
|
# st.caption(f"Sende {len(text_to_analyze)} Zeichen an API...")
|
||||||
|
|
||||||
with st.spinner("Analysiere..."):
|
with st.spinner("Analysiere..."):
|
||||||
# Aktuellen Text nehmen
|
|
||||||
text_to_analyze = st.session_state[data_body_key]
|
|
||||||
analysis = analyze_draft_text(text_to_analyze, meta_ref["type"])
|
analysis = analyze_draft_text(text_to_analyze, meta_ref["type"])
|
||||||
|
|
||||||
if "error" in analysis:
|
if "error" in analysis:
|
||||||
|
|
@ -342,23 +335,27 @@ def render_draft_editor(msg):
|
||||||
suggestions = analysis.get("suggestions", [])
|
suggestions = analysis.get("suggestions", [])
|
||||||
st.session_state[data_sugg_key] = suggestions
|
st.session_state[data_sugg_key] = suggestions
|
||||||
if not suggestions:
|
if not suggestions:
|
||||||
st.warning("Keine Vorschläge gefunden.")
|
st.warning("Keine Vorschläge gefunden (Text zu kurz oder keine Matches).")
|
||||||
else:
|
else:
|
||||||
st.success(f"{len(suggestions)} Vorschläge gefunden.")
|
st.success(f"{len(suggestions)} Vorschläge gefunden.")
|
||||||
|
|
||||||
|
# Render List
|
||||||
suggestions = st.session_state[data_sugg_key]
|
suggestions = st.session_state[data_sugg_key]
|
||||||
if suggestions:
|
if suggestions:
|
||||||
|
# Hole aktuellen Text für Vergleich
|
||||||
|
current_text_state = st.session_state.get(widget_body_key, "")
|
||||||
|
|
||||||
for idx, sugg in enumerate(suggestions):
|
for idx, sugg in enumerate(suggestions):
|
||||||
link_text = sugg.get('suggested_markdown', '')
|
link_text = sugg.get('suggested_markdown', '')
|
||||||
is_inserted = link_text in st.session_state[data_body_key]
|
is_inserted = link_text in current_text_state
|
||||||
|
|
||||||
card_style = "border-left: 3px solid #28a745;" if is_inserted else "border-left: 3px solid #1a73e8;"
|
|
||||||
bg_color = "#e6fffa" if is_inserted else "#ffffff"
|
bg_color = "#e6fffa" if is_inserted else "#ffffff"
|
||||||
|
border = "3px solid #28a745" if is_inserted else "3px solid #1a73e8"
|
||||||
|
|
||||||
st.markdown(f"""
|
st.markdown(f"""
|
||||||
<div style="{card_style} background-color: {bg_color}; padding: 10px; margin-bottom: 8px; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
<div style="border-left: {border}; background-color: {bg_color}; padding: 10px; margin-bottom: 8px; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
||||||
<b>{sugg.get('target_title', 'Unbekannt')}</b> <small>({sugg.get('type', 'semantic')})</small><br>
|
<b>{sugg.get('target_title')}</b> <small>({sugg.get('type')})</small><br>
|
||||||
<i>{sugg.get('reason', 'N/A')}</i><br>
|
<i>{sugg.get('reason')}</i><br>
|
||||||
<code>{link_text}</code>
|
<code>{link_text}</code>
|
||||||
</div>
|
</div>
|
||||||
""", unsafe_allow_html=True)
|
""", unsafe_allow_html=True)
|
||||||
|
|
@ -368,17 +365,18 @@ def render_draft_editor(msg):
|
||||||
else:
|
else:
|
||||||
st.button("➕ Einfügen", key=f"add_{idx}_{key_base}", on_click=_insert_text, args=(link_text,))
|
st.button("➕ Einfügen", key=f"add_{idx}_{key_base}", on_click=_insert_text, args=(link_text,))
|
||||||
|
|
||||||
# --- TAB 3: PREVIEW & SAVE ---
|
# --- TAB 3: SAVE ---
|
||||||
# Final Assembly
|
final_tags = [t.strip() for t in meta_ref["tags_str"].split(",") if t.strip()]
|
||||||
final_tags_list = [t.strip() for t in meta_ref["tags_str"].split(",") if t.strip()]
|
|
||||||
final_meta = {
|
final_meta = {
|
||||||
"id": "generated_on_save",
|
"id": "generated_on_save",
|
||||||
"type": meta_ref["type"],
|
"type": meta_ref["type"],
|
||||||
"title": meta_ref["title"],
|
"title": meta_ref["title"],
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"tags": final_tags_list
|
"tags": final_tags
|
||||||
}
|
}
|
||||||
final_doc = build_markdown_doc(final_meta, st.session_state[data_body_key])
|
# Auch hier: Nimm den aktuellsten Text für die Vorschau
|
||||||
|
final_body = st.session_state.get(widget_body_key, st.session_state[data_body_key])
|
||||||
|
final_doc = build_markdown_doc(final_meta, final_body)
|
||||||
|
|
||||||
with tab_view:
|
with tab_view:
|
||||||
st.markdown('<div class="preview-box">', unsafe_allow_html=True)
|
st.markdown('<div class="preview-box">', unsafe_allow_html=True)
|
||||||
|
|
@ -387,25 +385,13 @@ def render_draft_editor(msg):
|
||||||
|
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
b1, b2 = st.columns([1, 1])
|
|
||||||
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..."):
|
||||||
safe_title = re.sub(r'[^a-zA-Z0-9]', '-', meta_ref["title"]).lower()[:30]
|
safe_title = re.sub(r'[^a-zA-Z0-9]', '-', meta_ref["title"]).lower()[:30] 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"
|
||||||
|
res = save_draft_to_vault(final_doc, filename=fname)
|
||||||
result = save_draft_to_vault(final_doc, filename=fname)
|
if "error" in res: st.error(f"Fehler: {res['error']}")
|
||||||
if "error" in result:
|
else: st.success(f"Gespeichert: {res.get('file_path')}")
|
||||||
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_chat_interface(top_k, explain):
|
def render_chat_interface(top_k, explain):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user