/// Core Engram API routes — nodes, edges, activation, search. use axum::{ extract::{Path, State}, http::StatusCode, Json, }; use engram_core::types::{Edge, MemoryTier, Node, NodeType, RelationType}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use uuid::Uuid; use crate::state::AppState; // ── Stats ───────────────────────────────────────────────────────────────────── #[derive(Serialize)] pub struct StatsResponse { pub nodes: usize, pub edges: usize, } pub async fn get_stats( State(state): State>, ) -> Result, StatusCode> { let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let nodes = db.node_count().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let edges = db.edge_count().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(StatsResponse { nodes, edges })) } // ── Nodes ───────────────────────────────────────────────────────────────────── #[derive(Deserialize)] pub struct CreateNodeRequest { pub node_type: NodeType, pub embedding: Vec, pub content: Vec, pub tier: MemoryTier, pub importance: f32, } #[derive(Serialize)] pub struct CreateNodeResponse { pub id: Uuid, } pub async fn create_node( State(state): State>, Json(req): Json, ) -> Result, StatusCode> { let node = Node::new(req.node_type, req.embedding, req.content, req.tier, req.importance); let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let id = db.put_node(node).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(CreateNodeResponse { id })) } pub async fn get_node( State(state): State>, Path(id): Path, ) -> Result, StatusCode> { let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; match db.get_node(id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? { Some(node) => Ok(Json(node)), None => Err(StatusCode::NOT_FOUND), } } // ── Edges ───────────────────────────────────────────────────────────────────── #[derive(Deserialize)] pub struct CreateEdgeRequest { pub from_id: Uuid, pub to_id: Uuid, pub relation: RelationType, pub weight: f32, } pub async fn create_edge( State(state): State>, Json(req): Json, ) -> Result { let edge = Edge::new(req.from_id, req.to_id, req.relation, req.weight); let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; db.put_edge(edge).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(StatusCode::CREATED) } pub async fn get_edges_from( State(state): State>, Path(id): Path, ) -> Result>, StatusCode> { let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let edges = db.get_edges_from(id).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(edges)) } // ── Activation ──────────────────────────────────────────────────────────────── #[derive(Deserialize)] pub struct ActivateRequest { pub seeds: Vec, pub query_embedding: Vec, #[serde(default = "default_depth")] pub max_depth: u8, #[serde(default = "default_limit")] pub limit: usize, } fn default_depth() -> u8 { 3 } fn default_limit() -> usize { 10 } #[derive(Serialize)] pub struct ActivateResponse { pub results: Vec, } #[derive(Serialize)] pub struct ActivatedNodeJson { pub node: Node, pub activation_strength: f32, pub hops: u8, } pub async fn activate( State(state): State>, Json(req): Json, ) -> Result, StatusCode> { let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let results = db .activate(&req.seeds, &req.query_embedding, req.max_depth, req.limit) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(ActivateResponse { results: results .into_iter() .map(|a| ActivatedNodeJson { node: a.node, activation_strength: a.activation_strength, hops: a.hops, }) .collect(), })) } // ── Search ──────────────────────────────────────────────────────────────────── #[derive(Deserialize)] pub struct SearchRequest { pub embedding: Vec, #[serde(default = "default_limit")] pub limit: usize, } #[derive(Serialize)] pub struct SearchResponse { pub results: Vec, } #[derive(Serialize)] pub struct ScoredNodeJson { pub node: Node, pub score: f32, } pub async fn search_embedding( State(state): State>, Json(req): Json, ) -> Result, StatusCode> { let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let results = db .search_embedding(&req.embedding, req.limit) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(SearchResponse { results: results .into_iter() .map(|s| ScoredNodeJson { node: s.node, score: s.score }) .collect(), })) } // ── Decay / Consolidate ─────────────────────────────────────────────────────── #[derive(Deserialize)] pub struct DecayRequest { pub factor: f32, } #[derive(Serialize)] pub struct DecayResponse { pub nodes_updated: usize, } pub async fn decay( State(state): State>, Json(req): Json, ) -> Result, StatusCode> { let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let nodes_updated = db.decay(req.factor).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(DecayResponse { nodes_updated })) } #[derive(Serialize)] pub struct ConsolidateResponse { pub promoted: usize, } pub async fn consolidate( State(state): State>, ) -> Result, StatusCode> { use engram_core::ConsolidationConfig; let db = state.db.lock().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let report = db .consolidate(&ConsolidationConfig::default()) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(ConsolidateResponse { promoted: report.promoted })) }