#pragma once #include #include #include #include #include #include // Panel "Paste & Extract" (issue 0013). // // Textarea grande para pegar texto. Boton Extract lanza el script // `enrichers/paste_extract/run.py` en un hilo aparte (no bloquea UI). // El script devuelve un JSON con entidades y relaciones propuestas (modo // preview — no escribe a operations.db). El panel muestra dos tablas // (entidades / relaciones) con checkboxes; al pulsar "Apply Selected" // se persisten via entity_ops con dedupe por (type_ref, name). // // Threading: una llamada Extract a la vez (extract_busy bool). El hilo // rellena la propuesta tras hacerse el subprocess. Apply corre en el // thread principal y dispara reload del grafo via app.want_reload. // // El enricher esta declarado en `enrichers/paste_extract/manifest.yaml` // pero NO se invoca via el sistema de jobs — el panel lo lanza // directamente. Vivir en `enrichers/` permite que se distribuya y que // el script use el mismo Python runtime resolution que el resto. namespace ge { struct AppState; // Una entidad propuesta por el extractor. Se guarda como string para // poder editarla inline antes del Apply. struct ProposedEntity { std::string tmp_id; // "tmp_0", "tmp_1", ... vinculado a relaciones std::string type_ref; // editable std::string name; // editable std::string source; // "regex" | "hybrid" int start_offset = -1; // span en el texto pegado int end_offset = -1; double confidence = 1.0; std::string metadata_json; // JSON literal (no editable v1) bool selected = true; // Buffers mutables para edicion inline en ImGui. char type_buf[64] = {}; char name_buf[256] = {}; }; struct ProposedRelation { std::string from_tmp_id; std::string to_tmp_id; std::string name; // ej: "works_at" std::string source; // "hybrid" | ... double confidence = 0.0; bool selected = true; }; struct ExtractResult { std::vector entities; std::vector relations; std::vector layers; std::string error; // vacio si OK std::string stderr_tail; }; struct ExtractPanelState { // Buffer de texto del textarea. Crece dinamicamente. std::vector text_buf; bool text_initialized = false; // Resultado del ultimo Extract (poblado por el worker thread). std::shared_ptr result; std::mutex result_mu; std::atomic busy{false}; std::atomic new_result{false}; // hay resultado fresco // Mensaje de status (en el footer) — refrescado por el worker. std::string status; // Stats del ultimo apply. int last_apply_entities = 0; int last_apply_relations = 0; int last_apply_dedup = 0; // Toggle: ¿usar hybrid (GLiNER/GLiREL) si esta disponible? bool use_hybrid = false; // Worker thread; joinable cuando esta vivo. std::thread worker; }; // Configura paths que el worker necesita para invocar Python. Llamar una // vez tras `jobs_init` (re-usa el resolver de Python runtime + paths). void extract_panel_init(const char* enrichers_dir, const char* app_dir, const char* registry_root); // Suelta el worker thread si esta corriendo (cancelable). Llamar al // shutdown de la app. void extract_panel_shutdown(); // Renderiza el panel. Si app.panel_extract es false, retorna sin dibujar. void extract_panel_render(AppState& app); // Aplica las entidades/relaciones marcadas como selected al // operations.db indicado. Inserta entidades nuevas con dedupe por // (type_ref, name); reusa el id existente si lo encuentra. Despues // inserta las relaciones cuyos endpoints (mapeados via tmp_id -> // real_id) sean ambos validos. // // Devuelve los conteos en out_added_entities, out_dedup_entities, // out_added_relations. Tolera que algunas relaciones no resuelvan // (out_skipped_relations). El caller decide si setear app.want_reload. // // Esta funcion es testeable en aislamiento (no toca ImGui). bool extract_panel_apply(const char* ops_db_path, const ExtractResult& result, int* out_added_entities, int* out_dedup_entities, int* out_added_relations, int* out_skipped_relations); // Helper interno expuesto para tests: parsea el JSON que produce // `enrichers/paste_extract/run.py`. Devuelve true si el parseo es OK. // En error, result.error se rellena. bool extract_panel_parse_result(const std::string& json_text, ExtractResult* result); // Spawnea el subprocess Python para extraer. Sincronico (bloquea el // hilo del caller). El panel lo invoca en un std::thread aparte para // no congelar la UI. Expuesto por si los tests quieren llamarlo // directamente (no por ahora — los tests cubren el lado Python via // pytest, y el lado C++ via parse_result + apply). bool extract_panel_run_subprocess(const std::string& text, bool use_hybrid, ExtractResult* out); } // namespace ge