WP10 Überarbeitung
This commit is contained in:
parent
06f77fe8b7
commit
f4299db347
|
|
@ -23,7 +23,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.4", page_icon="🧠", layout="wide")
|
||||
|
||||
# --- CSS STYLING ---
|
||||
st.markdown("""
|
||||
|
|
@ -194,7 +194,7 @@ def analyze_draft_text(text: str, n_type: str):
|
|||
response = requests.post(
|
||||
INGEST_ANALYZE_ENDPOINT,
|
||||
json={"text": text, "type": n_type},
|
||||
timeout=15 # Erhöhtes Timeout für Suche
|
||||
timeout=15
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
|
@ -225,7 +225,7 @@ def submit_feedback(query_id, node_id, score, comment=None):
|
|||
def render_sidebar():
|
||||
with st.sidebar:
|
||||
st.title("🧠 mindnet")
|
||||
st.caption("v2.3.3 | WP-10b (Intelligence)")
|
||||
st.caption("v2.3.4 | WP-10b (Intelligence)")
|
||||
mode = st.radio("Modus", ["💬 Chat", "📝 Manueller Editor"], index=0)
|
||||
st.divider()
|
||||
st.subheader("⚙️ Settings")
|
||||
|
|
@ -243,7 +243,7 @@ def render_draft_editor(msg):
|
|||
qid = msg.get('query_id', str(uuid.uuid4()))
|
||||
key_base = f"draft_{qid}"
|
||||
|
||||
# 1. Init
|
||||
# 1. Init (Nur beim allerersten Laden)
|
||||
if f"{key_base}_init" not in st.session_state:
|
||||
meta, body = parse_markdown_draft(msg["content"])
|
||||
|
||||
|
|
@ -263,19 +263,89 @@ def render_draft_editor(msg):
|
|||
# Metadata
|
||||
c1, c2 = st.columns([2, 1])
|
||||
with c1:
|
||||
new_title = st.text_input("Titel", value=st.session_state.get(f"{key_base}_title", ""), key=f"{key_base}_inp_title")
|
||||
# Titel immer aus State lesen/schreiben
|
||||
new_title = st.text_input("Titel", key=f"{key_base}_inp_title", value=st.session_state.get(f"{key_base}_title", ""))
|
||||
with c2:
|
||||
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)
|
||||
new_type = st.selectbox("Typ", known_types, index=known_types.index(curr_type), key=f"{key_base}_sel_type")
|
||||
|
||||
new_tags = st.text_input("Tags (kommagetrennt)", value=st.session_state.get(f"{key_base}_tags", ""), key=f"{key_base}_inp_tags")
|
||||
new_tags = st.text_input("Tags (kommagetrennt)", key=f"{key_base}_inp_tags", value=st.session_state.get(f"{key_base}_tags", ""))
|
||||
|
||||
# Tabs (Jetzt mit "Intelligence")
|
||||
# Tabs
|
||||
tab_edit, tab_intel, tab_view = st.tabs(["✏️ Inhalt", "🧠 Intelligence", "👁️ Vorschau"])
|
||||
|
||||
# Live Reassembly für alle Tabs
|
||||
# --- TAB 1: EDITOR ---
|
||||
with tab_edit:
|
||||
# WICHTIG: Das Text-Area ist an session_state gebunden via 'key'.
|
||||
current_body = st.text_area(
|
||||
"Body",
|
||||
key=f"{key_base}_txt_body", # Master-Key
|
||||
value=st.session_state.get(f"{key_base}_body", ""),
|
||||
height=500,
|
||||
label_visibility="collapsed"
|
||||
)
|
||||
# Sync zurück zum generischen Key für andere Tabs
|
||||
st.session_state[f"{key_base}_body"] = current_body
|
||||
|
||||
# --- TAB 2: INTELLIGENCE ---
|
||||
with tab_intel:
|
||||
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"):
|
||||
with st.spinner("Analysiere..."):
|
||||
# Wir nehmen explizit den Text aus dem Widget-State
|
||||
text_to_analyze = st.session_state[f"{key_base}_txt_body"]
|
||||
analysis = analyze_draft_text(text_to_analyze, new_type)
|
||||
|
||||
if "error" in analysis:
|
||||
st.error(f"Fehler: {analysis['error']}")
|
||||
else:
|
||||
suggestions = analysis.get("suggestions", [])
|
||||
st.session_state[f"{key_base}_suggestions"] = suggestions
|
||||
if not suggestions:
|
||||
st.warning("Keine Vorschläge gefunden.")
|
||||
|
||||
suggestions = st.session_state.get(f"{key_base}_suggestions", [])
|
||||
if suggestions:
|
||||
st.write(f"**{len(suggestions)} Vorschläge:**")
|
||||
for idx, sugg in enumerate(suggestions):
|
||||
link_text = sugg.get('suggested_markdown', '')
|
||||
|
||||
# Check: Ist der Link schon im Text?
|
||||
is_inserted = link_text in st.session_state[f"{key_base}_txt_body"]
|
||||
|
||||
# Card Styling
|
||||
card_style = "border-left: 3px solid #28a745;" if is_inserted else "border-left: 3px solid #1a73e8;"
|
||||
bg_color = "#e6fffa" if is_inserted else "#ffffff"
|
||||
|
||||
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);">
|
||||
<b>{sugg.get('target_title', 'Unbekannt')}</b> <small>({sugg.get('type', 'semantic')})</small><br>
|
||||
<i>{sugg.get('reason', 'N/A')}</i><br>
|
||||
<code>{link_text}</code>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Button Logik (Toggle)
|
||||
if is_inserted:
|
||||
if st.button(f"❌ Entfernen", key=f"del_{idx}_{key_base}"):
|
||||
new_text = st.session_state[f"{key_base}_txt_body"].replace(link_text, "").strip()
|
||||
st.session_state[f"{key_base}_txt_body"] = new_text
|
||||
st.session_state[f"{key_base}_body"] = new_text
|
||||
st.rerun()
|
||||
else:
|
||||
if st.button(f"➕ Einfügen", key=f"add_{idx}_{key_base}"):
|
||||
old_text = st.session_state[f"{key_base}_txt_body"]
|
||||
new_text = f"{old_text}\n\n{link_text}"
|
||||
st.session_state[f"{key_base}_txt_body"] = new_text
|
||||
st.session_state[f"{key_base}_body"] = new_text
|
||||
st.rerun()
|
||||
|
||||
# --- TAB 3: PREVIEW & SAVE ---
|
||||
|
||||
# Reassemble Metadata & Body (Always use latest state)
|
||||
final_tags_list = [t.strip() for t in new_tags.split(",") if t.strip()]
|
||||
final_meta = {
|
||||
"id": "generated_on_save",
|
||||
|
|
@ -285,57 +355,10 @@ def render_draft_editor(msg):
|
|||
"tags": final_tags_list
|
||||
}
|
||||
|
||||
# --- TAB 1: EDITOR ---
|
||||
with tab_edit:
|
||||
new_body = st.text_area(
|
||||
"Body",
|
||||
value=st.session_state.get(f"{key_base}_body", ""),
|
||||
height=500,
|
||||
key=f"{key_base}_txt_body",
|
||||
label_visibility="collapsed"
|
||||
)
|
||||
# Wir nehmen den aktuellsten Body aus dem State
|
||||
final_body_content = st.session_state.get(f"{key_base}_txt_body", "")
|
||||
final_doc = build_markdown_doc(final_meta, final_body_content)
|
||||
|
||||
# --- TAB 2: INTELLIGENCE (WP-11 Features) ---
|
||||
with tab_intel:
|
||||
st.info("Klicke auf 'Analysieren', um Verknüpfungen zu finden.")
|
||||
|
||||
if st.button("🔍 Analyse starten", key=f"{key_base}_analyze"):
|
||||
with st.spinner("Analysiere Text und suche Verknüpfungen..."):
|
||||
current_text = st.session_state[f"{key_base}_body"]
|
||||
# API Call
|
||||
analysis = analyze_draft_text(current_text, new_type)
|
||||
|
||||
if "error" in analysis:
|
||||
st.error(f"Fehler: {analysis['error']}")
|
||||
else:
|
||||
suggestions = analysis.get("suggestions", [])
|
||||
st.session_state[f"{key_base}_suggestions"] = suggestions
|
||||
if not suggestions:
|
||||
st.warning("Keine offensichtlichen Verknüpfungen gefunden.")
|
||||
|
||||
# Anzeige der Vorschläge
|
||||
suggestions = st.session_state.get(f"{key_base}_suggestions", [])
|
||||
if suggestions:
|
||||
st.markdown(f"**{len(suggestions)} Vorschläge gefunden:**")
|
||||
for idx, sugg in enumerate(suggestions):
|
||||
with st.container():
|
||||
st.markdown(f"""
|
||||
<div class="suggestion-card">
|
||||
<b>{sugg.get('target_title', 'Unbekannt')}</b> <small>({sugg.get('type', 'semantic')})</small><br>
|
||||
<i>Grund: {sugg.get('reason', 'N/A')}</i><br>
|
||||
<code>{sugg.get('suggested_markdown', '')}</code>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
if st.button("➕ Einfügen", key=f"{key_base}_add_{idx}"):
|
||||
current_body = st.session_state[f"{key_base}_body"]
|
||||
updated_body = f"{current_body}\n\n{sugg['suggested_markdown']}"
|
||||
st.session_state[f"{key_base}_body"] = updated_body
|
||||
st.toast(f"Link zu '{sugg.get('target_title', '?')}' eingefügt!")
|
||||
st.rerun()
|
||||
|
||||
# --- TAB 3: PREVIEW ---
|
||||
final_doc = build_markdown_doc(final_meta, st.session_state.get(f"{key_base}_body", ""))
|
||||
with tab_view:
|
||||
st.markdown('<div class="preview-box">', unsafe_allow_html=True)
|
||||
st.markdown(final_doc)
|
||||
|
|
@ -343,24 +366,19 @@ def render_draft_editor(msg):
|
|||
|
||||
st.markdown("---")
|
||||
|
||||
# Actions (SAVE & EXPORT)
|
||||
# Save Action
|
||||
b1, b2 = st.columns([1, 1])
|
||||
with b1:
|
||||
# Echter Save Button (Ruft API auf)
|
||||
if st.button("💾 Speichern & Indizieren", type="primary", key=f"{key_base}_save"):
|
||||
with st.spinner("Speichere im Vault..."):
|
||||
# Generiere Filename
|
||||
safe_title = re.sub(r'[^a-zA-Z0-9]', '-', new_title).lower()[:30]
|
||||
fname = f"{datetime.now().strftime('%Y%m%d')}-{safe_title}.md"
|
||||
|
||||
# Wir holen den aktuellsten Stand aus dem State (inklusive eingefügter Links)
|
||||
latest_body = st.session_state.get(f"{key_base}_body", "")
|
||||
latest_doc = build_markdown_doc(final_meta, latest_body)
|
||||
|
||||
result = save_draft_to_vault(latest_doc, filename=fname)
|
||||
# Hier der entscheidende Call mit dem aktuellen Dokument
|
||||
result = save_draft_to_vault(final_doc, filename=fname)
|
||||
|
||||
if "error" in result:
|
||||
st.error(f"Fehler beim Speichern: {result['error']}")
|
||||
st.error(f"Fehler: {result['error']}")
|
||||
else:
|
||||
st.success(f"Gespeichert: {result.get('file_path')}")
|
||||
st.balloons()
|
||||
|
|
@ -447,6 +465,7 @@ def render_manual_editor():
|
|||
else:
|
||||
st.success(f"Gespeichert: {res.get('file_path')}")
|
||||
|
||||
# --- MAIN ---
|
||||
mode, top_k, explain = render_sidebar()
|
||||
if mode == "💬 Chat":
|
||||
render_chat_interface(top_k, explain)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
"""
|
||||
app/routers/ingest.py - DEBUG VERSION
|
||||
app/routers/ingest.py
|
||||
API-Endpunkte für WP-11 (Discovery & Persistence).
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Dict, Any
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from app.core.ingestion import IngestionService
|
||||
# Fallback: Falls DiscoveryService noch fehlt, nutzen wir Ingest Service Features oder Mock
|
||||
# Wir gehen hier davon aus, dass wir alles im IngestionService oder Router machen können,
|
||||
# um Importfehler zu vermeiden.
|
||||
from app.core.retriever import Retriever
|
||||
from app.models.dto import QueryRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# --- DTOs ---
|
||||
|
||||
class AnalyzeRequest(BaseModel):
|
||||
text: str
|
||||
type: str = "concept"
|
||||
|
|
@ -37,43 +43,36 @@ class SaveResponse(BaseModel):
|
|||
async def analyze_draft(req: AnalyzeRequest):
|
||||
"""
|
||||
WP-11 Intelligence: Liefert Link-Vorschläge.
|
||||
DEBUG MODE: Threshold gesenkt, Logging erhöht.
|
||||
Implementiert direkt hier, um Abhängigkeiten zu reduzieren.
|
||||
"""
|
||||
try:
|
||||
retriever = Retriever()
|
||||
suggestions = []
|
||||
|
||||
query_text = req.text[:400]
|
||||
logger.info(f"ANALYZING TEXT: '{query_text}' (Type: {req.type})")
|
||||
|
||||
if not query_text.strip():
|
||||
return {"suggestions": []}
|
||||
|
||||
# Wir suchen
|
||||
hits_result = await retriever.search(QueryRequest(query=query_text, top_k=5, mode="hybrid"))
|
||||
|
||||
logger.info(f"RETRIEVER FOUND: {len(hits_result.results)} raw hits")
|
||||
# 1. Semantic Search
|
||||
# Safe async call check
|
||||
if hasattr(retriever.search, '__await__'):
|
||||
hits_result = await retriever.search(QueryRequest(query=query_text, top_k=5, mode="hybrid"))
|
||||
else:
|
||||
hits_result = await retriever.search(QueryRequest(query=query_text, top_k=5, mode="hybrid"))
|
||||
|
||||
seen_titles = set()
|
||||
for hit in hits_result.results:
|
||||
# Titel holen
|
||||
title = hit.payload.get("title") or hit.payload.get("note_id") or hit.node_id
|
||||
|
||||
# Logging für jeden Treffer
|
||||
logger.info(f" -> CHECK HIT: {title} | Score: {hit.total_score:.4f}")
|
||||
|
||||
if not title or title in seen_titles:
|
||||
continue
|
||||
# Titel ermitteln
|
||||
title = hit.payload.get("note_id") or hit.node_id
|
||||
if not title or title in seen_titles: continue
|
||||
seen_titles.add(title)
|
||||
|
||||
# Edge Logic
|
||||
edge_kind = "related_to"
|
||||
if req.type == "project": edge_kind = "depends_on"
|
||||
if req.type == "decision": edge_kind = "references"
|
||||
|
||||
# --- ÄNDERUNG: THRESHOLD GESENKT ---
|
||||
# War vorher 0.65. Jetzt 0.3 für Tests.
|
||||
if hit.total_score > 0.3:
|
||||
# Score Threshold
|
||||
if hit.total_score > 0.4: # Etwas toleranter
|
||||
suggestions.append({
|
||||
"target_title": title,
|
||||
"target_id": hit.node_id,
|
||||
|
|
@ -81,19 +80,19 @@ async def analyze_draft(req: AnalyzeRequest):
|
|||
"reason": f"Semantisch ähnlich ({hit.total_score:.2f})",
|
||||
"type": "semantic"
|
||||
})
|
||||
else:
|
||||
logger.info(f" -> SKIPPED (Score too low)")
|
||||
|
||||
logger.info(f"RETURNING {len(suggestions)} SUGGESTIONS")
|
||||
return {"suggestions": suggestions}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Analyze failed: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
||||
# Kein 500er werfen, lieber leere Liste, damit UI nicht crasht
|
||||
return {"suggestions": [], "error": str(e)}
|
||||
|
||||
@router.post("/save", response_model=SaveResponse)
|
||||
async def save_note(req: SaveRequest):
|
||||
"""WP-11 Persistence"""
|
||||
"""
|
||||
WP-11 Persistence: Speichert Markdown physisch und indiziert es sofort.
|
||||
"""
|
||||
try:
|
||||
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
||||
abs_vault_root = os.path.abspath(vault_root)
|
||||
|
|
@ -106,19 +105,28 @@ async def save_note(req: SaveRequest):
|
|||
final_filename = f"draft_{int(time.time())}.md"
|
||||
|
||||
ingest_service = IngestionService()
|
||||
logger.info(f"Saving {final_filename}")
|
||||
|
||||
result = await ingest_service.save_and_index(
|
||||
markdown_content=req.markdown_content,
|
||||
filename=final_filename
|
||||
)
|
||||
|
||||
logger.info(f"Saving {final_filename} to {req.folder}")
|
||||
|
||||
# --- AWAIT WICHTIG! ---
|
||||
# Wir rufen save_and_index auf (so hieß es in meiner IngestionService Implementierung)
|
||||
# Wenn deine Methode create_from_text heißt, ändere es hier entsprechend.
|
||||
# Ich nutze hier save_and_index als Standard aus WP-11.
|
||||
|
||||
if hasattr(ingest_service, 'save_and_index'):
|
||||
result = await ingest_service.save_and_index(req.markdown_content, final_filename)
|
||||
elif hasattr(ingest_service, 'create_from_text'):
|
||||
# Fallback falls du die alte Version hast
|
||||
result = await ingest_service.create_from_text(req.markdown_content, final_filename, abs_vault_root, req.folder)
|
||||
else:
|
||||
raise RuntimeError("IngestionService hat weder save_and_index noch create_from_text")
|
||||
|
||||
if result.get("status") == "error":
|
||||
raise HTTPException(status_code=500, detail=result.get("error"))
|
||||
|
||||
return SaveResponse(
|
||||
status="success",
|
||||
file_path=result.get("file_path", "unknown"),
|
||||
file_path=result.get("file_path") or result.get("path", "unknown"),
|
||||
note_id=result.get("note_id", "unknown"),
|
||||
stats=result.get("stats", {})
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user