scriptAudit #11
|
|
@ -1,10 +1,10 @@
|
|||
"""
|
||||
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.
|
||||
VERSION: 2.6.0
|
||||
DESCRIPTION: Markdown-Editor mit Live-Vorschau.
|
||||
Refactored für WP-14: Asynchrones Feedback-Handling (Queued State).
|
||||
VERSION: 2.7.0 (Fix: Async Save UI)
|
||||
STATUS: Active
|
||||
DEPENDENCIES: streamlit, uuid, re, datetime, ui_utils, ui_api
|
||||
LAST_ANALYSIS: 2025-12-15
|
||||
"""
|
||||
import streamlit as st
|
||||
import uuid
|
||||
|
|
@ -76,14 +76,11 @@ def render_draft_editor(msg):
|
|||
|
||||
# --- 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 Expander
|
||||
with st.expander("Dateipfad Details", expanded=False):
|
||||
st.code(origin_fname)
|
||||
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"
|
||||
|
||||
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:
|
||||
# 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)
|
||||
|
||||
# --- WP-14 CHANGE START: Handling Async Response ---
|
||||
if "error" in result:
|
||||
st.error(f"Fehler: {result['error']}")
|
||||
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()
|
||||
# --- WP-14 CHANGE END ---
|
||||
|
||||
with b2:
|
||||
if st.button("📋 Code anzeigen", key=f"{key_base}_btn_copy"):
|
||||
st.code(final_doc, language="markdown")
|
||||
|
|
@ -197,25 +206,18 @@ def render_draft_editor(msg):
|
|||
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
|
||||
"query_id": f"manual_{uuid.uuid4()}"
|
||||
}
|
||||
|
||||
render_draft_editor(target_msg)
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
"""
|
||||
FILE: app/routers/ingest.py
|
||||
DESCRIPTION: Endpunkte für WP-11. Nimmt Markdown entgegen, steuert Ingestion und Discovery (Link-Vorschläge).
|
||||
VERSION: 0.6.0
|
||||
DESCRIPTION: Endpunkte für WP-11. Nimmt Markdown entgegen.
|
||||
Refactored für WP-14: Nutzt BackgroundTasks für non-blocking Save.
|
||||
VERSION: 0.7.0 (Fix: Timeout WP-14)
|
||||
STATUS: Active
|
||||
DEPENDENCIES: app.core.ingestion, app.services.discovery, fastapi, pydantic
|
||||
LAST_ANALYSIS: 2025-12-15
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from fastapi import APIRouter, HTTPException
|
||||
import asyncio
|
||||
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ from app.services.discovery import DiscoveryService
|
|||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
# Services Init (Global oder via Dependency Injection)
|
||||
# Services Init
|
||||
discovery_service = DiscoveryService()
|
||||
|
||||
class AnalyzeRequest(BaseModel):
|
||||
|
|
@ -36,7 +37,32 @@ class SaveResponse(BaseModel):
|
|||
status: str
|
||||
file_path: 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")
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
# Hier rufen wir jetzt den verbesserten Service auf
|
||||
result = await discovery_service.analyze_draft(req.text, req.type)
|
||||
return result
|
||||
except Exception as e:
|
||||
|
|
@ -52,9 +77,10 @@ async def analyze_draft(req: AnalyzeRequest):
|
|||
return {"suggestions": [], "error": str(e)}
|
||||
|
||||
@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:
|
||||
vault_root = os.getenv("MINDNET_VAULT_ROOT", "./vault")
|
||||
|
|
@ -65,29 +91,31 @@ async def save_note(req: SaveRequest):
|
|||
except: pass
|
||||
|
||||
final_filename = req.filename or f"draft_{int(time.time())}.md"
|
||||
ingest_service = IngestionService()
|
||||
|
||||
# Async Call
|
||||
result = await ingest_service.create_from_text(
|
||||
# Wir geben sofort eine ID zurück (optimistisch),
|
||||
# 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,
|
||||
filename=final_filename,
|
||||
vault_root=abs_vault_root,
|
||||
folder=req.folder
|
||||
)
|
||||
|
||||
if result.get("status") == "error":
|
||||
raise HTTPException(status_code=500, detail=result.get("error"))
|
||||
|
||||
return SaveResponse(
|
||||
status="success",
|
||||
file_path=result.get("path", "unknown"),
|
||||
note_id=result.get("note_id", "unknown"),
|
||||
status="queued",
|
||||
file_path=os.path.join(req.folder, final_filename),
|
||||
note_id="pending",
|
||||
message="Speicherung & KI-Analyse im Hintergrund gestartet.",
|
||||
stats={
|
||||
"chunks": result.get("chunks_count", 0),
|
||||
"edges": result.get("edges_count", 0)
|
||||
"chunks": -1, # Indikator für Async
|
||||
"edges": -1
|
||||
}
|
||||
)
|
||||
except HTTPException as he: raise he
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Save failed: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Save failed: {str(e)}")
|
||||
logger.error(f"Save dispatch failed: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"Save dispatch failed: {str(e)}")
|
||||
Loading…
Reference in New Issue
Block a user