bug fix
This commit is contained in:
parent
c6ccad1d18
commit
ccc848f2e2
|
|
@ -10,30 +10,33 @@ from ui_api import save_draft_to_vault, analyze_draft_text, send_chat_message, s
|
||||||
from ui_config import HISTORY_FILE, COLLECTION_PREFIX, GRAPH_COLORS
|
from ui_config import HISTORY_FILE, COLLECTION_PREFIX, GRAPH_COLORS
|
||||||
|
|
||||||
# --- CALLBACKS ---
|
# --- CALLBACKS ---
|
||||||
# Diese müssen oben definiert sein, damit sie VOR dem Re-Run bekannt sind.
|
|
||||||
|
|
||||||
def switch_to_editor_callback(note_payload):
|
def switch_to_editor_callback(note_payload):
|
||||||
"""
|
"""
|
||||||
Callback-Funktion: Wird ausgeführt, wenn der 'Bearbeiten'-Button geklickt wird.
|
Lädt eine Note in den Editor.
|
||||||
Da dies ein Callback ist, können wir session_state Werte ändern, bevor die UI neu gezeichnet wird.
|
Versucht, den Original-Dateinamen zu erraten oder zu finden, um Duplikate zu vermeiden.
|
||||||
"""
|
"""
|
||||||
# 1. Inhalt vorbereiten
|
# 1. Inhalt holen
|
||||||
content = note_payload.get('fulltext', '')
|
content = note_payload.get('fulltext', '')
|
||||||
if not content:
|
if not content:
|
||||||
# Fallback: Markdown aus Metadaten rekonstruieren, falls kein Fulltext da ist
|
|
||||||
content = build_markdown_doc(note_payload, "Inhalt konnte nicht geladen werden (nur Metadaten verfügbar).")
|
content = build_markdown_doc(note_payload, "Inhalt konnte nicht geladen werden (nur Metadaten verfügbar).")
|
||||||
|
|
||||||
# 2. Nachricht simulieren (als ob der Chatbot sie generiert hätte)
|
# 2. Dateinamen-Heuristik (Single Source of Truth)
|
||||||
# Dies füllt den Editor mit dem Inhalt der Notiz
|
# Idealfall: Qdrant hat das Feld 'file_path' oder 'filename' gespeichert.
|
||||||
|
# Fallback: Wir nutzen die note_id oder den Titel, müssen aber beim Speichern aufpassen.
|
||||||
|
origin_fname = note_payload.get('file_path') or note_payload.get('filename')
|
||||||
|
|
||||||
|
# Nachricht simulieren, die Daten in den Editor trägt
|
||||||
st.session_state.messages.append({
|
st.session_state.messages.append({
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"intent": "INTERVIEW",
|
"intent": "INTERVIEW",
|
||||||
"content": content,
|
"content": content,
|
||||||
"query_id": f"edit_{note_payload['note_id']}"
|
"query_id": f"edit_{note_payload['note_id']}",
|
||||||
|
"origin_filename": origin_fname, # WICHTIG: Pfad mitschleifen
|
||||||
|
"origin_note_id": note_payload['note_id'] # ID für Fallback mitschleifen
|
||||||
})
|
})
|
||||||
|
|
||||||
# 3. Modus umschalten
|
# 3. Modus umschalten
|
||||||
# Das ist der entscheidende Fix: Wir ändern den Wert des Radio-Buttons im State direkt.
|
|
||||||
st.session_state["sidebar_mode_selection"] = "📝 Manueller Editor"
|
st.session_state["sidebar_mode_selection"] = "📝 Manueller Editor"
|
||||||
|
|
||||||
# --- UI RENDERER ---
|
# --- UI RENDERER ---
|
||||||
|
|
@ -44,7 +47,6 @@ def render_sidebar():
|
||||||
st.caption("v2.6 | WP-19 Graph View")
|
st.caption("v2.6 | WP-19 Graph View")
|
||||||
|
|
||||||
# State-gebundenes Radio Widget
|
# State-gebundenes Radio Widget
|
||||||
# Wir nutzen 'sidebar_mode_selection' als Key, damit wir ihn programmgesteuert (Callback) ändern können.
|
|
||||||
if "sidebar_mode_selection" not in st.session_state:
|
if "sidebar_mode_selection" not in st.session_state:
|
||||||
st.session_state["sidebar_mode_selection"] = "💬 Chat"
|
st.session_state["sidebar_mode_selection"] = "💬 Chat"
|
||||||
|
|
||||||
|
|
@ -69,7 +71,7 @@ def render_sidebar():
|
||||||
|
|
||||||
def render_draft_editor(msg):
|
def render_draft_editor(msg):
|
||||||
"""
|
"""
|
||||||
Der Editor-Kern. Wird sowohl im Chat (Interview-Modus) als auch im manuellen Modus verwendet.
|
Smart Editor: Unterscheidet zwischen 'Neu' und 'Update'.
|
||||||
"""
|
"""
|
||||||
if "query_id" not in msg or not msg["query_id"]:
|
if "query_id" not in msg or not msg["query_id"]:
|
||||||
msg["query_id"] = str(uuid.uuid4())
|
msg["query_id"] = str(uuid.uuid4())
|
||||||
|
|
@ -77,7 +79,7 @@ def render_draft_editor(msg):
|
||||||
qid = msg["query_id"]
|
qid = msg["query_id"]
|
||||||
key_base = f"draft_{qid}"
|
key_base = f"draft_{qid}"
|
||||||
|
|
||||||
# State Keys für Persistenz
|
# State Keys
|
||||||
data_meta_key = f"{key_base}_data_meta"
|
data_meta_key = f"{key_base}_data_meta"
|
||||||
data_sugg_key = f"{key_base}_data_suggestions"
|
data_sugg_key = f"{key_base}_data_suggestions"
|
||||||
widget_body_key = f"{key_base}_widget_body"
|
widget_body_key = f"{key_base}_widget_body"
|
||||||
|
|
@ -85,27 +87,40 @@ def render_draft_editor(msg):
|
||||||
|
|
||||||
# --- INIT STATE ---
|
# --- INIT STATE ---
|
||||||
if f"{key_base}_init" not in st.session_state:
|
if f"{key_base}_init" not in st.session_state:
|
||||||
|
# Metadaten parsen
|
||||||
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"] = ""
|
||||||
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)
|
||||||
|
|
||||||
|
# Daten in Session State laden
|
||||||
st.session_state[data_meta_key] = meta
|
st.session_state[data_meta_key] = meta
|
||||||
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()
|
||||||
|
|
||||||
# Widget States initialisieren
|
# Widget States
|
||||||
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"]
|
||||||
|
|
||||||
|
# --- EDITOR LOGIK: Origin Filename ---
|
||||||
|
# Wir speichern den Original-Namen im State, um beim Speichern zu wissen, ob wir überschreiben müssen.
|
||||||
|
origin_file = msg.get("origin_filename")
|
||||||
|
if not origin_file and "origin_note_id" in msg:
|
||||||
|
# Fallback: Wenn wir keinen Pfad haben, aber eine ID, merken wir uns diese,
|
||||||
|
# um später ggf. intelligent zu speichern (z.B. {id}.md suchen)
|
||||||
|
# Hier vereinfacht: Wir setzen es erstmal auf None, User muss aufpassen.
|
||||||
|
pass
|
||||||
|
|
||||||
|
st.session_state[f"{key_base}_origin_filename"] = origin_file
|
||||||
st.session_state[f"{key_base}_init"] = True
|
st.session_state[f"{key_base}_init"] = True
|
||||||
|
|
||||||
# --- STATE RESURRECTION ---
|
# --- RESURRECTION ---
|
||||||
if widget_body_key not in st.session_state and data_body_key in st.session_state:
|
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]
|
st.session_state[widget_body_key] = st.session_state[data_body_key]
|
||||||
|
|
||||||
# --- CALLBACKS ---
|
# --- SYNC FUNCTIONS ---
|
||||||
def _sync_meta():
|
def _sync_meta():
|
||||||
meta = st.session_state[data_meta_key]
|
meta = st.session_state[data_meta_key]
|
||||||
meta["title"] = st.session_state.get(f"{key_base}_wdg_title", "")
|
meta["title"] = st.session_state.get(f"{key_base}_wdg_title", "")
|
||||||
|
|
@ -129,11 +144,19 @@ def render_draft_editor(msg):
|
||||||
st.session_state[data_body_key] = new_text
|
st.session_state[data_body_key] = new_text
|
||||||
|
|
||||||
# --- UI LAYOUT ---
|
# --- UI LAYOUT ---
|
||||||
|
|
||||||
|
# Header: Status anzeigen
|
||||||
|
origin_fname = st.session_state.get(f"{key_base}_origin_filename")
|
||||||
|
if origin_fname:
|
||||||
|
st.info(f"📝 Bearbeitungs-Modus: Du editierst **{origin_fname}**")
|
||||||
|
st.markdown(f'<div class="draft-box" style="border-left: 5px solid #ff9f43;">', unsafe_allow_html=True)
|
||||||
|
else:
|
||||||
|
st.info("✨ Neuer Entwurf (Wird als neue Datei angelegt)")
|
||||||
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("### Editor")
|
||||||
|
|
||||||
meta_ref = st.session_state[data_meta_key]
|
meta_ref = st.session_state[data_meta_key]
|
||||||
|
|
||||||
c1, c2 = st.columns([2, 1])
|
c1, c2 = st.columns([2, 1])
|
||||||
with c1:
|
with c1:
|
||||||
st.text_input("Titel", key=f"{key_base}_wdg_title", on_change=_sync_meta)
|
st.text_input("Titel", key=f"{key_base}_wdg_title", on_change=_sync_meta)
|
||||||
|
|
@ -174,7 +197,6 @@ def render_draft_editor(msg):
|
||||||
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 current_text_state
|
is_inserted = link_text in current_text_state
|
||||||
|
|
||||||
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"
|
border = "3px solid #28a745" if is_inserted else "3px solid #1a73e8"
|
||||||
|
|
||||||
|
|
@ -201,6 +223,11 @@ def render_draft_editor(msg):
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"tags": final_tags
|
"tags": final_tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ID wiederherstellen, falls vorhanden
|
||||||
|
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])
|
final_body = st.session_state.get(widget_body_key, st.session_state[data_body_key])
|
||||||
|
|
||||||
if not final_meta["title"]:
|
if not final_meta["title"]:
|
||||||
|
|
@ -218,20 +245,33 @@ def render_draft_editor(msg):
|
||||||
|
|
||||||
b1, b2 = st.columns([1, 1])
|
b1, b2 = st.columns([1, 1])
|
||||||
with b1:
|
with b1:
|
||||||
if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"):
|
# Button Text dynamisch machen
|
||||||
|
btn_label = "💾 Update speichern" if origin_fname else "💾 Neu anlegen & Indizieren"
|
||||||
|
|
||||||
|
if st.button(btn_label, type="primary", key=f"{key_base}_save"):
|
||||||
with st.spinner("Speichere im Vault..."):
|
with st.spinner("Speichere im Vault..."):
|
||||||
|
|
||||||
|
# ENTSCHEIDUNG: Update oder Neu?
|
||||||
|
if origin_fname:
|
||||||
|
# UPDATE: Wir nutzen den existierenden Dateinamen
|
||||||
|
target_filename = origin_fname
|
||||||
|
else:
|
||||||
|
# NEU: Wir generieren einen Namen
|
||||||
raw_title = final_meta.get("title", "")
|
raw_title = final_meta.get("title", "")
|
||||||
if not raw_title:
|
if not raw_title:
|
||||||
clean_body = re.sub(r"[#*_\[\]()]", "", final_body).strip()
|
clean_body = re.sub(r"[#*_\[\]()]", "", final_body).strip()
|
||||||
raw_title = clean_body[:40] if clean_body else "draft"
|
raw_title = clean_body[:40] if clean_body else "draft"
|
||||||
safe_title = slugify(raw_title)[:60] or "draft"
|
safe_title = slugify(raw_title)[:60] or "draft"
|
||||||
fname = f"{datetime.now().strftime('%Y%m%d')}-{safe_title}.md"
|
target_filename = 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=target_filename)
|
||||||
if "error" in result: st.error(f"Fehler: {result['error']}")
|
|
||||||
|
if "error" in result:
|
||||||
|
st.error(f"Fehler: {result['error']}")
|
||||||
else:
|
else:
|
||||||
st.success(f"Gespeichert: {result.get('file_path')}")
|
st.success(f"Gespeichert: {result.get('file_path')}")
|
||||||
st.balloons()
|
st.balloons()
|
||||||
|
|
||||||
with b2:
|
with b2:
|
||||||
if st.button("📋 Code anzeigen", key=f"{key_base}_btn_copy"):
|
if st.button("📋 Code anzeigen", key=f"{key_base}_btn_copy"):
|
||||||
st.code(final_doc, language="markdown")
|
st.code(final_doc, language="markdown")
|
||||||
|
|
@ -239,9 +279,6 @@ def render_draft_editor(msg):
|
||||||
st.markdown("</div>", unsafe_allow_html=True)
|
st.markdown("</div>", unsafe_allow_html=True)
|
||||||
|
|
||||||
def render_chat_interface(top_k, explain):
|
def render_chat_interface(top_k, explain):
|
||||||
"""
|
|
||||||
Rendert den Chat-Verlauf und das Eingabefeld.
|
|
||||||
"""
|
|
||||||
for idx, msg in enumerate(st.session_state.messages):
|
for idx, msg in enumerate(st.session_state.messages):
|
||||||
with st.chat_message(msg["role"]):
|
with st.chat_message(msg["role"]):
|
||||||
if msg["role"] == "assistant":
|
if msg["role"] == "assistant":
|
||||||
|
|
@ -298,7 +335,6 @@ def render_chat_interface(top_k, explain):
|
||||||
st.rerun()
|
st.rerun()
|
||||||
|
|
||||||
def render_manual_editor():
|
def render_manual_editor():
|
||||||
"""Rendert einen leeren Editor für manuelle Eingaben."""
|
|
||||||
mock_msg = {
|
mock_msg = {
|
||||||
"content": "---\ntype: concept\ntitle: Neue Notiz\nstatus: draft\ntags: []\n---\n# Titel\n",
|
"content": "---\ntype: concept\ntitle: Neue Notiz\nstatus: draft\ntags: []\n---\n# Titel\n",
|
||||||
"query_id": "manual_mode_v2"
|
"query_id": "manual_mode_v2"
|
||||||
|
|
@ -310,10 +346,8 @@ def render_manual_editor():
|
||||||
def render_graph_explorer(graph_service):
|
def render_graph_explorer(graph_service):
|
||||||
st.header("🕸️ Graph Explorer")
|
st.header("🕸️ Graph Explorer")
|
||||||
|
|
||||||
# State Init
|
|
||||||
if "graph_center_id" not in st.session_state: st.session_state.graph_center_id = None
|
if "graph_center_id" not in st.session_state: st.session_state.graph_center_id = None
|
||||||
|
|
||||||
# Defaults speichern für Persistenz während der Session
|
|
||||||
st.session_state.setdefault("graph_depth", 2)
|
st.session_state.setdefault("graph_depth", 2)
|
||||||
st.session_state.setdefault("graph_show_labels", True)
|
st.session_state.setdefault("graph_show_labels", True)
|
||||||
st.session_state.setdefault("graph_spacing", 200)
|
st.session_state.setdefault("graph_spacing", 200)
|
||||||
|
|
@ -324,7 +358,6 @@ def render_graph_explorer(graph_service):
|
||||||
with col_ctrl:
|
with col_ctrl:
|
||||||
st.subheader("Fokus")
|
st.subheader("Fokus")
|
||||||
|
|
||||||
# 1. Suchfeld
|
|
||||||
search_term = st.text_input("Suche Notiz", placeholder="Titel eingeben...")
|
search_term = st.text_input("Suche Notiz", placeholder="Titel eingeben...")
|
||||||
|
|
||||||
options = {}
|
options = {}
|
||||||
|
|
@ -344,7 +377,6 @@ def render_graph_explorer(graph_service):
|
||||||
|
|
||||||
st.divider()
|
st.divider()
|
||||||
|
|
||||||
# --- VIEW SETTINGS ---
|
|
||||||
with st.expander("👁️ Ansicht & Layout", expanded=True):
|
with st.expander("👁️ Ansicht & Layout", expanded=True):
|
||||||
st.session_state.graph_depth = st.slider("Tiefe (Tier)", 1, 3, st.session_state.graph_depth)
|
st.session_state.graph_depth = st.slider("Tiefe (Tier)", 1, 3, st.session_state.graph_depth)
|
||||||
st.session_state.graph_show_labels = st.checkbox("Kanten-Beschriftung", st.session_state.graph_show_labels)
|
st.session_state.graph_show_labels = st.checkbox("Kanten-Beschriftung", st.session_state.graph_show_labels)
|
||||||
|
|
@ -367,12 +399,11 @@ def render_graph_explorer(graph_service):
|
||||||
center_id = st.session_state.graph_center_id
|
center_id = st.session_state.graph_center_id
|
||||||
|
|
||||||
if center_id:
|
if center_id:
|
||||||
# Action Bar
|
|
||||||
c_action1, c_action2 = st.columns([3, 1])
|
c_action1, c_action2 = st.columns([3, 1])
|
||||||
with c_action1:
|
with c_action1:
|
||||||
st.caption(f"Aktives Zentrum: **{center_id}**")
|
st.caption(f"Aktives Zentrum: **{center_id}**")
|
||||||
with c_action2:
|
with c_action2:
|
||||||
# FIX: Button mit Callback (on_click)
|
# Button mit Callback (on_click)
|
||||||
note_data = graph_service._fetch_note_cached(center_id)
|
note_data = graph_service._fetch_note_cached(center_id)
|
||||||
if note_data:
|
if note_data:
|
||||||
st.button("📝 Bearbeiten",
|
st.button("📝 Bearbeiten",
|
||||||
|
|
@ -390,11 +421,10 @@ def render_graph_explorer(graph_service):
|
||||||
)
|
)
|
||||||
|
|
||||||
if not nodes:
|
if not nodes:
|
||||||
st.warning("Keine Daten gefunden. (Notiz existiert evtl. nicht mehr)")
|
st.warning("Keine Daten gefunden.")
|
||||||
else:
|
else:
|
||||||
# FIX: Dynamischer Key erzwingt Neu-Rendern bei Physics-Änderung
|
# FIX: Key entfernt, da er in deiner Version TypeError verursacht.
|
||||||
graph_key = f"graph_{center_id}_{st.session_state.graph_gravity}_{st.session_state.graph_spacing}"
|
# Wir verlassen uns auf Config-Update.
|
||||||
|
|
||||||
config = Config(
|
config = Config(
|
||||||
width=1000,
|
width=1000,
|
||||||
height=800,
|
height=800,
|
||||||
|
|
@ -404,7 +434,6 @@ def render_graph_explorer(graph_service):
|
||||||
nodeHighlightBehavior=True,
|
nodeHighlightBehavior=True,
|
||||||
highlightColor="#F7A7A6",
|
highlightColor="#F7A7A6",
|
||||||
collapsible=False,
|
collapsible=False,
|
||||||
# Solver Wechsel: ForceAtlas2Based
|
|
||||||
solver="forceAtlas2Based",
|
solver="forceAtlas2Based",
|
||||||
forceAtlas2Based={
|
forceAtlas2Based={
|
||||||
"theta": 0.5,
|
"theta": 0.5,
|
||||||
|
|
@ -418,7 +447,7 @@ def render_graph_explorer(graph_service):
|
||||||
stabilization={"enabled": True, "iterations": 800}
|
stabilization={"enabled": True, "iterations": 800}
|
||||||
)
|
)
|
||||||
|
|
||||||
return_value = agraph(nodes=nodes, edges=edges, config=config, key=graph_key)
|
return_value = agraph(nodes=nodes, edges=edges, config=config)
|
||||||
|
|
||||||
if return_value:
|
if return_value:
|
||||||
if return_value != center_id:
|
if return_value != center_id:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user