feagi-api 0.0.12

FEAGI REST API layer with HTTP and ZMQ transport adapters
Documentation
// Copyright 2025 Neuraville Inc.
// SPDX-License-Identifier: Apache-2.0

/*!
 * FEAGI v1 Visualization API
 *
 * Endpoints for managing visualization clients and streams
 * Maps to Python: feagi/api/v1/visualization.py
 */

use crate::common::ApiState;
use crate::common::{ApiError, ApiResult, Json, State};
// Removed - using crate::common::State instead
use serde_json::{json, Value};
use std::collections::HashMap;

// ============================================================================
// VISUALIZATION CLIENT MANAGEMENT
// ============================================================================

/// Register a new visualization client for receiving neural activity streams.
#[utoipa::path(
    post,
    path = "/v1/visualization/register_client",
    tag = "visualization",
    responses(
        (status = 200, description = "Client registered", body = HashMap<String, serde_json::Value>),
        (status = 500, description = "Internal server error")
    )
)]
pub async fn post_register_client(
    State(_state): State<ApiState>,
    Json(request): Json<HashMap<String, Value>>,
) -> ApiResult<Json<HashMap<String, Value>>> {
    // Extract or generate client_id
    let client_id = request
        .get("client_id")
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());

    // TODO: Register visualization client with actual state management
    tracing::info!(target: "feagi-api", "Registered visualization client: {}", client_id);

    let mut response = HashMap::new();
    response.insert("client_id".to_string(), json!(client_id));
    response.insert("success".to_string(), json!(true));
    response.insert(
        "message".to_string(),
        json!("Visualization client registered successfully"),
    );

    Ok(Json(response))
}

/// Unregister a visualization client to stop receiving neural activity streams.
#[utoipa::path(
    post,
    path = "/v1/visualization/unregister_client",
    tag = "visualization",
    responses(
        (status = 200, description = "Client unregistered", body = HashMap<String, String>),
        (status = 500, description = "Internal server error")
    )
)]
pub async fn post_unregister_client(
    State(_state): State<ApiState>,
    Json(request): Json<HashMap<String, Value>>,
) -> ApiResult<Json<HashMap<String, String>>> {
    // Validate client_id is provided
    let client_id = request
        .get("client_id")
        .and_then(|v| v.as_str())
        .ok_or_else(|| ApiError::invalid_input("Missing 'client_id' field"))?;

    // TODO: Unregister visualization client
    tracing::info!(target: "feagi-api", "Unregistered visualization client: {}", client_id);

    Ok(Json(HashMap::from([(
        "message".to_string(),
        "Visualization client unregistered successfully".to_string(),
    )])))
}

/// Send heartbeat from visualization client to maintain active connection.
#[utoipa::path(
    post,
    path = "/v1/visualization/heartbeat",
    tag = "visualization",
    responses(
        (status = 200, description = "Heartbeat received", body = HashMap<String, String>),
        (status = 500, description = "Internal server error")
    )
)]
pub async fn post_heartbeat(
    State(_state): State<ApiState>,
    Json(_request): Json<HashMap<String, Value>>,
) -> ApiResult<Json<HashMap<String, String>>> {
    // TODO: Process visualization heartbeat

    Ok(Json(HashMap::from([(
        "message".to_string(),
        "Heartbeat received".to_string(),
    )])))
}

/// Get visualization system status including active clients and FQ sampler state.
#[utoipa::path(
    get,
    path = "/v1/visualization/status",
    tag = "visualization",
    responses(
        (status = 200, description = "Visualization status", body = HashMap<String, serde_json::Value>),
        (status = 500, description = "Internal server error")
    )
)]
pub async fn get_status(State(_state): State<ApiState>) -> ApiResult<Json<HashMap<String, Value>>> {
    // TODO: Retrieve visualization status
    let mut response = HashMap::new();
    response.insert("enabled".to_string(), json!(false));
    response.insert("active_clients".to_string(), json!(0));
    response.insert("fq_sampler_enabled".to_string(), json!(false));
    response.insert("message".to_string(), json!("Visualization system idle"));

    Ok(Json(response))
}