from flask import Blueprint, render_template, request, jsonify, redirect, flash from modules.core.security import requires_admin_auth from modules.core.audit import audit_log import threading from modules.core.audit import audit_log from oci_genai_llm_graphrag_rerank_rfp import ( search_chunks_for_invalidation, revoke_chunk_by_hash, get_chunk_metadata, add_manual_knowledge_entry, reload_all ) admin_bp = Blueprint("admin", __name__) # ========================= # ADMIN HOME (invalidate UI) # ========================= @admin_bp.route("/") @requires_admin_auth def admin_home(): return render_template("admin_menu.html") @admin_bp.route("/invalidate") @requires_admin_auth def invalidate_page(): return render_template( "invalidate.html", results=[], statement="" ) # ========================= # SEARCH CHUNKS # ========================= @admin_bp.route("/search", methods=["POST"]) @requires_admin_auth def search_for_invalidation(): statement = request.form["statement"] docs = search_chunks_for_invalidation(statement) hashes = [d.metadata.get("chunk_hash") for d in docs if d.metadata.get("chunk_hash")] meta = get_chunk_metadata(hashes) results = [] for d in docs: h = d.metadata.get("chunk_hash") m = meta.get(h, {}) results.append({ "chunk_hash": h, "source": d.metadata.get("source"), "text": d.page_content, "origin": m.get("origin"), "status": m.get("status") }) return render_template( "invalidate.html", statement=statement, results=results ) # ========================= # REVOKE # ========================= @admin_bp.route("/revoke", methods=["POST"]) @requires_admin_auth def revoke_chunk_ui(): data = request.get_json() chunk_hash = str(data["chunk_hash"]) reason = str(data.get("reason", "Manual revoke")) audit_log("INVALIDATE", f"chunk_hash={chunk_hash}") print("chunk_hash", chunk_hash) print("reason", reason) revoke_chunk_by_hash(chunk_hash, reason) return {"status": "ok", "chunk_hash": chunk_hash} # ========================= # ADD MANUAL KNOWLEDGE # ========================= @admin_bp.route("/add-knowledge", methods=["POST"]) @requires_admin_auth def add_manual_knowledge(): data = request.get_json(force=True) chunk_hash = add_manual_knowledge_entry( text=data["text"], author="ADMIN", reason=data.get("reason"), source="MANUAL_INPUT", origin="MANUAL", also_update_graph=True ) audit_log("ADD_KNOWLEDGE", f"chunk_hash={chunk_hash}") return jsonify({ "status": "OK", "chunk_hash": chunk_hash }) # ========================= # UPDATE CHUNK # ========================= @admin_bp.route("/update-chunk", methods=["POST"]) @requires_admin_auth def update_chunk(): data = request.get_json() or {} chunk_hash = str(data.get("chunk_hash", "")).strip() text = str(data.get("text", "")).strip() print("chunk_hash", chunk_hash) print("text", text) if not chunk_hash: return {"status": "error", "message": "missing hash"}, 400 reason = str(data.get("reason", "Manual change")) revoke_chunk_by_hash(chunk_hash, reason=reason) chunk_hash = add_manual_knowledge_entry( text=text, author="ADMIN", reason=reason, source="MANUAL_INPUT", origin="MANUAL", also_update_graph=True ) audit_log("UPDATE CHUNK", f"chunk_hash={chunk_hash}") return jsonify({ "status": "OK", "chunk_hash": chunk_hash }) @admin_bp.route("/reboot", methods=["POST"]) @requires_admin_auth def reboot_service(): # roda em background pra não travar request threading.Thread(target=reload_all, daemon=True).start() return jsonify({ "status": "ok", "message": "Knowledge reload started" })