feat: port el-ui vessels — rename crates→vessels, add El source + manifests
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user