139 lines
6.9 KiB
EmacsLisp
139 lines
6.9 KiB
EmacsLisp
// editor.el — Round-trip graph editing API.
|
|
//
|
|
// Provides El functions for mutating the Engram graph (the live knowledge graph
|
|
// stored in-process via engram_* builtins). These functions are the mutation
|
|
// layer for graph editors — the CGI Studio Engram panel will call these to add,
|
|
// remove, and connect nodes without reloading the whole graph.
|
|
//
|
|
// All mutations write directly to the in-process Engram via engram_* builtins
|
|
// (see BOOTSTRAP.md §Engram Knowledge Graph).
|
|
//
|
|
// Drag interaction is NOT implemented here — that requires pointer-event
|
|
// handlers in JavaScript. When el-ui-compiler gains a JS backend, wire the
|
|
// move_node() position cache to the layout state keys used in layout.el.
|
|
//
|
|
// Public API:
|
|
// graph_add_node(content, node_type, label, salience) -> String // node_id or ""
|
|
// graph_remove_node(node_id) -> String // "ok" or error JSON
|
|
// graph_add_edge(from_id, to_id, weight_str, relation) -> String // "ok" or error JSON
|
|
// graph_remove_edge(from_id, to_id) -> String // "ok" or error JSON
|
|
// graph_move_node(node_id, x_str, y_str) -> String // "ok" (position cache)
|
|
|
|
// ── graph_add_node ────────────────────────────────────────────────────────────
|
|
|
|
fn graph_add_node(content: String, node_type: String, label: String, salience_str: String) -> String {
|
|
if str_eq(content, "") {
|
|
return "{\"error\":\"content is required\"}"
|
|
}
|
|
let sal: Float = if str_eq(salience_str, "") { int_to_float(1) } else { str_to_float(salience_str) }
|
|
// engram_node_full: content, type, label, salience, importance, confidence, tier, tags
|
|
let eff_label: String = if str_eq(label, "") { str_slice(content, 0, 40) } else { label }
|
|
let eff_type: String = if str_eq(node_type, "") { "Entity" } else { node_type }
|
|
let node_id: String = engram_node_full(content, eff_type, eff_label, sal, sal, int_to_float(1), "Working", "")
|
|
if str_eq(node_id, "") {
|
|
return "{\"error\":\"engram_node_full returned empty id\"}"
|
|
}
|
|
"{\"id\":\"" + node_id + "\"}"
|
|
}
|
|
|
|
// ── graph_remove_node ─────────────────────────────────────────────────────────
|
|
|
|
fn graph_remove_node(node_id: String) -> String {
|
|
if str_eq(node_id, "") {
|
|
return "{\"error\":\"node_id is required\"}"
|
|
}
|
|
// Check node exists
|
|
let existing: String = engram_get_node(node_id)
|
|
if str_eq(existing, "") {
|
|
return "{\"error\":\"node not found\",\"id\":\"" + node_id + "\"}"
|
|
}
|
|
engram_forget(node_id)
|
|
"{\"ok\":true,\"id\":\"" + node_id + "\"}"
|
|
}
|
|
|
|
// ── graph_add_edge ────────────────────────────────────────────────────────────
|
|
|
|
fn graph_add_edge(from_id: String, to_id: String, weight_str: String, relation: String) -> String {
|
|
if str_eq(from_id, "") {
|
|
return "{\"error\":\"from_id is required\"}"
|
|
}
|
|
if str_eq(to_id, "") {
|
|
return "{\"error\":\"to_id is required\"}"
|
|
}
|
|
let w: Float = if str_eq(weight_str, "") { int_to_float(1) } else { str_to_float(weight_str) }
|
|
let rel: String = if str_eq(relation, "") { "relates_to" } else { relation }
|
|
// engram_connect(from, to, weight, relation)
|
|
let edge_id: String = engram_connect(from_id, to_id, w, rel)
|
|
if str_eq(edge_id, "") {
|
|
return "{\"error\":\"engram_connect failed\",\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\"}"
|
|
}
|
|
"{\"ok\":true,\"edge_id\":\"" + edge_id + "\",\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\"}"
|
|
}
|
|
|
|
// ── graph_remove_edge ─────────────────────────────────────────────────────────
|
|
|
|
fn graph_remove_edge(from_id: String, to_id: String) -> String {
|
|
if str_eq(from_id, "") {
|
|
return "{\"error\":\"from_id is required\"}"
|
|
}
|
|
if str_eq(to_id, "") {
|
|
return "{\"error\":\"to_id is required\"}"
|
|
}
|
|
// Check edge exists
|
|
let existing: String = engram_edge_between(from_id, to_id)
|
|
if str_eq(existing, "") {
|
|
return "{\"error\":\"edge not found\",\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\"}"
|
|
}
|
|
// No engram_remove_edge builtin — use engram_forget on the edge node if
|
|
// an edge ID was returned, otherwise surface a not-implemented note.
|
|
// In practice, engram_forget(node_id) removes a node and its edges;
|
|
// there is no "remove edge only" primitive yet.
|
|
"{\"error\":\"remove_edge not yet supported by engram builtins — remove the node to remove all its edges\",\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\"}"
|
|
}
|
|
|
|
// ── graph_move_node ───────────────────────────────────────────────────────────
|
|
//
|
|
// Caches a node's screen position for use by the layout engine.
|
|
// When el-ui-compiler ships JS output, drag handlers will call this after
|
|
// pointer-up to persist the dragged position so the next render uses it as
|
|
// the initial position (preventing snap-back after re-layout).
|
|
|
|
fn graph_move_node(node_id: String, x_str: String, y_str: String) -> String {
|
|
if str_eq(node_id, "") {
|
|
return "{\"error\":\"node_id is required\"}"
|
|
}
|
|
// Store in process state — layout.el reads these keys as initial positions.
|
|
state_set("node_x_" + node_id, x_str)
|
|
state_set("node_y_" + node_id, y_str)
|
|
// Also zero the velocity so the node doesn't immediately drift.
|
|
state_set("node_vx_" + node_id, "0.0")
|
|
state_set("node_vy_" + node_id, "0.0")
|
|
"{\"ok\":true,\"id\":\"" + node_id + "\",\"x\":" + x_str + ",\"y\":" + y_str + "}"
|
|
}
|
|
|
|
// ── graph_node_info ───────────────────────────────────────────────────────────
|
|
//
|
|
// Retrieve full node data from Engram (for inspector panels).
|
|
|
|
fn graph_node_info(node_id: String) -> String {
|
|
if str_eq(node_id, "") {
|
|
return "{\"error\":\"node_id is required\"}"
|
|
}
|
|
let n: String = engram_get_node(node_id)
|
|
if str_eq(n, "") {
|
|
return "{\"error\":\"node not found\",\"id\":\"" + node_id + "\"}"
|
|
}
|
|
n
|
|
}
|
|
|
|
// ── graph_neighbors ───────────────────────────────────────────────────────────
|
|
//
|
|
// Return the neighbors of a node as a JSON array (for sub-graph drill-down).
|
|
|
|
fn graph_neighbors_json(node_id: String) -> String {
|
|
if str_eq(node_id, "") {
|
|
return "{\"error\":\"node_id is required\"}"
|
|
}
|
|
engram_neighbors(node_id)
|
|
}
|