Files
graph_explorer/entity_ops.h
T
egutierrez 54aba71bf5 feat(filter): tag chips + FTS5 search en toolbar (issue 0009)
- entity_ops: entity_search_fts (bm25, prefix tokens) + entity_list_by_tags (AND).
- AppState: filter_query_buf, filter_tags, filter_mode (highlight/hide), hits cache.
- views_filter_apply: combina tipos visibles + match-set (FTS query AND tags),
  highlight = color_override con alpha=0x40, hide = clear NF_VISIBLE.
- toolbar: input search + dropdown (max 20, click centra y selecciona),
  chips de tags con boton X, input para anadir tag, combo Highlight/Hide,
  Clear filter.
- Inspector: right-click sobre tag chip lo anade al filtro.
- main.cpp: reapply en cada frame si filter_dirty; cam_x/y al focus_target.
2026-05-01 00:23:26 +02:00

138 lines
5.4 KiB
C++

#pragma once
#include <cstdint>
#include <cstddef>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
// Operaciones CRUD sobre operations.db (entities + relations) y deteccion
// heuristica de tipo a partir de texto libre. Pensado para que la toolbar y
// el menu contextual del viewport puedan modificar el grafo y luego pedir
// reload (issue 0049g flow).
//
// Convencion edge labels: si el caller no pasa nombre de relacion, se usa
// k_default_relation_name = "RELATED_TO". Los enrichers deben pasar siempre
// un nombre semantico (ej: "EXTRACTED_FROM", "RESOLVES_TO", ...).
namespace ge {
constexpr const char* k_default_relation_name = "RELATED_TO";
enum DetectedType {
DT_TEXT = 0,
DT_EMAIL,
DT_IP_ADDRESS,
DT_URL,
DT_DOMAIN,
DT_PHONE,
};
DetectedType detect_type(const char* text);
const char* detected_type_name(DetectedType dt);
// Inserta una entidad nueva. Si type_ref es NULL/vacio se infiere via
// detect_type(name). Genera un id unico ("<type>_<unix_ms>"). Devuelve el id
// en out_id (caller-owned buffer >= 64). Retorna false si SQLite falla.
bool entity_insert(const char* db_path,
const char* name,
const char* type_ref,
char* out_id, size_t out_id_n);
bool entity_delete(const char* db_path, const char* id);
bool entity_update_type(const char* db_path, const char* id, const char* new_type);
// Duplica una entidad existente. Mismo type/metadata, sufijo "_copy" en id
// y "(copia)" en name. Devuelve el nuevo id en out_id.
bool entity_duplicate(const char* db_path, const char* id,
char* out_id, size_t out_id_n);
// Inserta una relacion. Si name es NULL/vacio usa k_default_relation_name.
bool relation_insert(const char* db_path,
const char* from_id, const char* to_id,
const char* name);
// Lee la columna `notes` (markdown) de una entidad. out se reasigna; vacio si
// no existe la entidad.
bool entity_get_notes(const char* db_path, const char* id, std::string* out);
// Sobrescribe `notes` con el contenido proporcionado. Toca `updated_at`.
bool entity_set_notes(const char* db_path, const char* id, const char* notes);
// ----------------------------------------------------------------------------
// Inspector editable (issue 0008)
// ----------------------------------------------------------------------------
// Un campo de la columna `metadata` JSON. `is_string=true` se emite como
// "..." en JSON; false se emite como literal (number/bool/null). El parser
// rellena esto al leer; el caller puede sobreescribirlo basado en el schema
// del tipo antes de guardar.
struct MetadataField {
std::string key;
std::string value_str;
bool is_string = true;
};
// Snapshot completo de los campos editables de una entidad. No incluye
// `notes` (panel separado) ni `created_at` (no editable).
struct EntityRecord {
std::string id;
std::string name;
std::string type_ref;
std::string description;
std::string status; // active|stale|corrupted|archived
std::vector<std::string> tags;
std::vector<MetadataField> metadata; // orden preservado del JSON original
};
// Carga el snapshot editable de la entidad. Devuelve false si no existe o
// SQLite falla. Tags y metadata se parsean desde JSON.
bool entity_load_full(const char* db_path, const char* id, EntityRecord* out);
// Persiste el snapshot. Toca `updated_at`. Tags y metadata se serializan a
// JSON. Devuelve false si SQLite falla.
bool entity_update(const char* db_path, const EntityRecord& rec);
// Lista las tags distintas en uso en toda la BD (para autocomplete del
// chip-input del Inspector). Sin orden particular.
bool entity_list_distinct_tags(const char* db_path,
std::vector<std::string>* out);
// Resultado de busqueda FTS5 sobre entities_fts.
struct EntityHit {
std::string id;
std::string name;
std::string type_ref;
double rank = 0.0; // bm25 (mas bajo = mejor)
};
// Busca en entities_fts (columnas: id, name, description, tags, domain).
// Tokeniza `query` por whitespace, descarta caracteres especiales FTS5 y
// aplica busqueda por prefijo a cada token (token*). Limit clampeado en [1,200].
// Devuelve hits ordenados por rank ASC. Si query queda vacio tras sanear,
// devuelve true con out->empty(). Si la BD no tiene la tabla entities_fts
// (operations.db antiguos), devuelve false.
bool entity_search_fts(const char* db_path, const char* query, int limit,
std::vector<EntityHit>* out);
// Devuelve los ids de entidades cuyo array `tags` contiene TODOS los tags
// pasados (AND). Si `tags` vacio, out queda vacio y retorna true.
bool entity_list_by_tags(const char* db_path,
const std::vector<std::string>& tags,
std::vector<std::string>* out_ids);
// Mapa user_data (FNV1a hash) -> sql id. Se reconstruye despues de cada
// carga del grafo (graph_sources usa FNV1a sobre id como user_data).
struct EntityIndex {
std::unordered_map<uint64_t, std::string> by_hash;
};
// Escanea operations.db y rellena el indice. Reentrante (clear+repoblar).
bool entity_index_build(const char* db_path, EntityIndex* idx);
// Resuelve user_data a sql id. NULL si no existe.
const char* entity_index_lookup(const EntityIndex& idx, uint64_t user_data);
} // namespace ge