neue ui Save-logik
This commit is contained in:
parent
7639bb8472
commit
6011b96fc1
|
|
@ -1,10 +1,10 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/frontend/ui_editor.py
|
FILE: app/frontend/ui_editor.py
|
||||||
DESCRIPTION: Markdown-Editor mit Live-Vorschau und Metadaten-Feldern. Unterstützt Intelligence-Features (Link-Vorschläge) und unterscheidet Create/Update-Modus.
|
DESCRIPTION: Markdown-Editor mit Live-Vorschau.
|
||||||
VERSION: 2.6.0
|
Refactored für WP-14: Asynchrones Feedback-Handling (Queued State).
|
||||||
|
VERSION: 2.7.0 (Fix: Async Save UI)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
DEPENDENCIES: streamlit, uuid, re, datetime, ui_utils, ui_api
|
DEPENDENCIES: streamlit, uuid, re, datetime, ui_utils, ui_api
|
||||||
LAST_ANALYSIS: 2025-12-15
|
|
||||||
"""
|
"""
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
import uuid
|
import uuid
|
||||||
|
|
@ -76,14 +76,11 @@ def render_draft_editor(msg):
|
||||||
|
|
||||||
# --- UI LAYOUT ---
|
# --- UI LAYOUT ---
|
||||||
|
|
||||||
# Header Info (Debug Pfad anzeigen, damit wir sicher sind)
|
|
||||||
origin_fname = st.session_state.get(f"{key_base}_origin_filename")
|
origin_fname = st.session_state.get(f"{key_base}_origin_filename")
|
||||||
|
|
||||||
if origin_fname:
|
if origin_fname:
|
||||||
# Dateiname extrahieren für saubere Anzeige
|
|
||||||
display_name = str(origin_fname).split("/")[-1]
|
display_name = str(origin_fname).split("/")[-1]
|
||||||
st.success(f"📂 **Update-Modus**: `{display_name}`")
|
st.success(f"📂 **Update-Modus**: `{display_name}`")
|
||||||
# Debugging: Zeige vollen Pfad im Expander
|
|
||||||
with st.expander("Dateipfad Details", expanded=False):
|
with st.expander("Dateipfad Details", expanded=False):
|
||||||
st.code(origin_fname)
|
st.code(origin_fname)
|
||||||
st.markdown(f'<div class="draft-box" style="border-left: 5px solid #ff9f43;">', unsafe_allow_html=True)
|
st.markdown(f'<div class="draft-box" style="border-left: 5px solid #ff9f43;">', unsafe_allow_html=True)
|
||||||
|
|
@ -173,21 +170,33 @@ def render_draft_editor(msg):
|
||||||
save_label = "💾 Update speichern" if origin_fname else "💾 Neu anlegen & Indizieren"
|
save_label = "💾 Update speichern" if origin_fname else "💾 Neu anlegen & Indizieren"
|
||||||
|
|
||||||
if st.button(save_label, type="primary", key=f"{key_base}_save"):
|
if st.button(save_label, type="primary", key=f"{key_base}_save"):
|
||||||
with st.spinner("Speichere im Vault..."):
|
with st.spinner("Sende an Backend..."):
|
||||||
if origin_fname:
|
if origin_fname:
|
||||||
# UPDATE: Ziel ist der exakte Pfad
|
|
||||||
target_file = origin_fname
|
target_file = origin_fname
|
||||||
else:
|
else:
|
||||||
# CREATE: Neuer Dateiname
|
|
||||||
raw_title = final_meta.get("title", "draft")
|
raw_title = final_meta.get("title", "draft")
|
||||||
target_file = f"{datetime.now().strftime('%Y%m%d')}-{slugify(raw_title)[:60]}.md"
|
target_file = f"{datetime.now().strftime('%Y%m%d')}-{slugify(raw_title)[:60]}.md"
|
||||||
|
|
||||||
result = save_draft_to_vault(final_doc, filename=target_file)
|
result = save_draft_to_vault(final_doc, filename=target_file)
|
||||||
|
|
||||||
|
# --- WP-14 CHANGE START: Handling Async Response ---
|
||||||
if "error" in result:
|
if "error" in result:
|
||||||
st.error(f"Fehler: {result['error']}")
|
st.error(f"Fehler: {result['error']}")
|
||||||
else:
|
else:
|
||||||
st.success(f"Gespeichert: {result.get('file_path')}")
|
status = result.get("status", "success")
|
||||||
|
file_path = result.get("file_path", "unbekannt")
|
||||||
|
|
||||||
|
if status == "queued":
|
||||||
|
# Neuer Status für Async Processing
|
||||||
|
st.info(f"✅ **Eingereiht:** Datei `{file_path}` wurde gespeichert.")
|
||||||
|
st.caption("Die KI-Analyse und Indizierung läuft im Hintergrund. Du kannst weiterarbeiten.")
|
||||||
|
else:
|
||||||
|
# Legacy / Synchroner Fall
|
||||||
|
st.success(f"Gespeichert: {file_path}")
|
||||||
|
|
||||||
st.balloons()
|
st.balloons()
|
||||||
|
# --- WP-14 CHANGE END ---
|
||||||
|
|
||||||
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")
|
||||||
|
|
@ -197,25 +206,18 @@ def render_draft_editor(msg):
|
||||||
def render_manual_editor():
|
def render_manual_editor():
|
||||||
"""
|
"""
|
||||||
Rendert den manuellen Editor.
|
Rendert den manuellen Editor.
|
||||||
PRÜFT, ob eine Edit-Anfrage aus dem Graphen vorliegt!
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
target_msg = None
|
target_msg = None
|
||||||
|
|
||||||
# 1. Prüfen: Gibt es Nachrichten im Verlauf?
|
|
||||||
if st.session_state.messages:
|
if st.session_state.messages:
|
||||||
last_msg = st.session_state.messages[-1]
|
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", ""))
|
qid = str(last_msg.get("query_id", ""))
|
||||||
if qid.startswith("edit_"):
|
if qid.startswith("edit_"):
|
||||||
target_msg = last_msg
|
target_msg = last_msg
|
||||||
|
|
||||||
# 3. Fallback: Leeres Template, falls keine Edit-Anfrage vorliegt
|
|
||||||
if not target_msg:
|
if not target_msg:
|
||||||
target_msg = {
|
target_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": f"manual_{uuid.uuid4()}" # Eigene ID, damit neuer State entsteht
|
"query_id": f"manual_{uuid.uuid4()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
render_draft_editor(target_msg)
|
render_draft_editor(target_msg)
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
"""
|
"""
|
||||||
FILE: app/routers/ingest.py
|
FILE: app/routers/ingest.py
|
||||||
DESCRIPTION: Endpunkte für WP-11. Nimmt Markdown entgegen, steuert Ingestion und Discovery (Link-Vorschläge).
|
DESCRIPTION: Endpunkte für WP-11. Nimmt Markdown entgegen.
|
||||||
VERSION: 0.6.0
|
Refactored für WP-14: Nutzt BackgroundTasks für non-blocking Save.
|
||||||
|
VERSION: 0.7.0 (Fix: Timeout WP-14)
|
||||||
STATUS: Active
|
STATUS: Active
|
||||||
DEPENDENCIES: app.core.ingestion, app.services.discovery, fastapi, pydantic
|
DEPENDENCIES: app.core.ingestion, app.services.discovery, fastapi, pydantic
|
||||||
LAST_ANALYSIS: 2025-12-15
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
from fastapi import APIRouter, HTTPException
|
import asyncio
|
||||||
|
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ from app.services.discovery import DiscoveryService
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
# Services Init (Global oder via Dependency Injection)
|
# Services Init
|
||||||
discovery_service = DiscoveryService()
|
discovery_service = DiscoveryService()
|
||||||
|
|
||||||
class AnalyzeRequest(BaseModel):
|
class AnalyzeRequest(BaseModel):
|
||||||
|
|
@ -36,7 +37,32 @@ class SaveResponse(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
file_path: str
|
file_path: str
|
||||||
note_id: str
|
note_id: str
|
||||||
stats: Dict[str, Any]
|
message: str # Neu für UX Feedback
|
||||||
|
stats: Dict[str, Any] # Kann leer sein bei async processing
|
||||||
|
|
||||||
|
# --- Background Task Wrapper ---
|
||||||
|
async def run_ingestion_task(markdown_content: str, filename: str, vault_root: str, folder: str):
|
||||||
|
"""
|
||||||
|
Führt die Ingestion im Hintergrund aus, damit der Request nicht blockiert.
|
||||||
|
"""
|
||||||
|
logger.info(f"🔄 Background Task started: Ingesting {filename}...")
|
||||||
|
try:
|
||||||
|
ingest_service = IngestionService()
|
||||||
|
result = await ingest_service.create_from_text(
|
||||||
|
markdown_content=markdown_content,
|
||||||
|
filename=filename,
|
||||||
|
vault_root=vault_root,
|
||||||
|
folder=folder
|
||||||
|
)
|
||||||
|
# Hier könnte man später Notification-Services (Websockets) triggern
|
||||||
|
if result.get("status") == "error":
|
||||||
|
logger.error(f"❌ Background Ingestion Error for {filename}: {result.get('error')}")
|
||||||
|
else:
|
||||||
|
logger.info(f"✅ Background Task finished: {filename} ({result.get('chunks_count')} Chunks)")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"❌ Critical Background Task Failure: {e}", exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/analyze")
|
@router.post("/analyze")
|
||||||
async def analyze_draft(req: AnalyzeRequest):
|
async def analyze_draft(req: AnalyzeRequest):
|
||||||
|
|
@ -44,7 +70,6 @@ async def analyze_draft(req: AnalyzeRequest):
|
||||||
WP-11 Intelligence: Liefert Link-Vorschläge via DiscoveryService.
|
WP-11 Intelligence: Liefert Link-Vorschläge via DiscoveryService.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Hier rufen wir jetzt den verbesserten Service auf
|
|
||||||
result = await discovery_service.analyze_draft(req.text, req.type)
|
result = await discovery_service.analyze_draft(req.text, req.type)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -52,9 +77,10 @@ async def analyze_draft(req: AnalyzeRequest):
|
||||||
return {"suggestions": [], "error": str(e)}
|
return {"suggestions": [], "error": str(e)}
|
||||||
|
|
||||||
@router.post("/save", response_model=SaveResponse)
|
@router.post("/save", response_model=SaveResponse)
|
||||||
async def save_note(req: SaveRequest):
|
async def save_note(req: SaveRequest, background_tasks: BackgroundTasks):
|
||||||
"""
|
"""
|
||||||
WP-11 Persistence: Speichert und indiziert.
|
WP-14 Fix: Startet Ingestion im Hintergrund (Fire & Forget).
|
||||||
|
Verhindert Timeouts bei aktiver Smart-Edge-Allocation (WP-15).
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
||||||
|
|
@ -65,29 +91,31 @@ async def save_note(req: SaveRequest):
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
final_filename = req.filename or f"draft_{int(time.time())}.md"
|
final_filename = req.filename or f"draft_{int(time.time())}.md"
|
||||||
ingest_service = IngestionService()
|
|
||||||
|
|
||||||
# Async Call
|
# Wir geben sofort eine ID zurück (optimistisch),
|
||||||
result = await ingest_service.create_from_text(
|
# auch wenn die echte ID erst nach dem Parsing feststeht.
|
||||||
|
# Für UI-Feedback nutzen wir den Filename.
|
||||||
|
|
||||||
|
# Task in die Queue schieben
|
||||||
|
background_tasks.add_task(
|
||||||
|
run_ingestion_task,
|
||||||
markdown_content=req.markdown_content,
|
markdown_content=req.markdown_content,
|
||||||
filename=final_filename,
|
filename=final_filename,
|
||||||
vault_root=abs_vault_root,
|
vault_root=abs_vault_root,
|
||||||
folder=req.folder
|
folder=req.folder
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.get("status") == "error":
|
|
||||||
raise HTTPException(status_code=500, detail=result.get("error"))
|
|
||||||
|
|
||||||
return SaveResponse(
|
return SaveResponse(
|
||||||
status="success",
|
status="queued",
|
||||||
file_path=result.get("path", "unknown"),
|
file_path=os.path.join(req.folder, final_filename),
|
||||||
note_id=result.get("note_id", "unknown"),
|
note_id="pending",
|
||||||
|
message="Speicherung & KI-Analyse im Hintergrund gestartet.",
|
||||||
stats={
|
stats={
|
||||||
"chunks": result.get("chunks_count", 0),
|
"chunks": -1, # Indikator für Async
|
||||||
"edges": result.get("edges_count", 0)
|
"edges": -1
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except HTTPException as he: raise he
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Save failed: {e}", exc_info=True)
|
logger.error(f"Save dispatch failed: {e}", exc_info=True)
|
||||||
raise HTTPException(status_code=500, detail=f"Save failed: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Save dispatch failed: {str(e)}")
|
||||||
Loading…
Reference in New Issue
Block a user