- Complete UCXL protocol implementation with DHT storage layer - BZZZ Gateway for peer-to-peer networking and content distribution - Temporal navigation engine with version control and timeline browsing - Standardized UCXL error/response codes for Rust, Go, and Python - React-based UI with multi-tab interface and professional styling - libp2p integration for distributed hash table operations - Self-healing network mechanisms and peer management - Comprehensive IPC commands for Tauri desktop integration Major Components: - ucxl-core: Core UCXL protocol and DHT implementation - BZZZ Gateway: Local subnet peer discovery and content replication - Temporal Engine: Version control and state reconstruction - Cross-language standards: Unified error handling across implementations - Modern UI: Professional React interface with DHT and network monitoring Standards Compliance: - UCXL-ERROR-CODES.md and UCXL-RESPONSE-CODES.md v1.0 - Machine-readable error codes with structured payloads - Client guidance for retry logic and error handling - Cross-language compatibility with identical APIs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
611 lines
23 KiB
Rust
611 lines
23 KiB
Rust
use serde::{Serialize, Deserialize};
|
|
use std::collections::HashMap;
|
|
use std::sync::{Arc, Mutex};
|
|
use ucxl_core::*;
|
|
|
|
// Helper functions for creating standard responses
|
|
fn create_error_response(code: UCXLErrorCode, path: &str, request_id: Option<&str>) -> CoreToUiResponse {
|
|
let error = UCXLErrorBuilder::new(code)
|
|
.source("ucxl-browser")
|
|
.path(path)
|
|
.request_id(request_id.unwrap_or("unknown"))
|
|
.build();
|
|
CoreToUiResponse::Error(error)
|
|
}
|
|
|
|
fn create_success_response<T>(code: UCXLResponseCode, data: Option<T>, request_id: Option<&str>) -> CoreToUiResponse
|
|
where T: serde::Serialize
|
|
{
|
|
let response = UCXLResponseBuilder::new(code)
|
|
.data(serde_json::to_value(data).unwrap_or(serde_json::Value::Null))
|
|
.request_id(request_id.unwrap_or("unknown"))
|
|
.build();
|
|
CoreToUiResponse::Success(response)
|
|
}
|
|
|
|
fn create_envelope_error(code: UCXLErrorCode, envelope_id: &str, path: &str) -> CoreToUiResponse {
|
|
let error = UCXLErrorBuilder::new(code)
|
|
.field("envelope_id", envelope_id.into())
|
|
.source("ucxl-browser")
|
|
.path(path)
|
|
.build();
|
|
CoreToUiResponse::Error(error)
|
|
}
|
|
|
|
fn create_uri_error(uri: &str, path: &str) -> CoreToUiResponse {
|
|
let error = UCXLErrorBuilder::new(UCXLErrorCode::InvalidAddress)
|
|
.field("address", uri.into())
|
|
.expected_format("ucxl://<agent>:<role>@<project>:<task>[/<temporal>/<path>]")
|
|
.source("ucxl-browser")
|
|
.path(path)
|
|
.cause("parse_error")
|
|
.build();
|
|
CoreToUiResponse::Error(error)
|
|
}
|
|
|
|
// Global state for the application
|
|
#[derive(Clone)]
|
|
pub struct AppState {
|
|
pub store: Arc<Mutex<InMemoryEnvelopeStore>>,
|
|
pub dht_store: Arc<Mutex<Option<DHTEnvelopeStore>>>,
|
|
pub bzzz_gateway: Arc<Mutex<Option<BZZZGateway>>>,
|
|
pub temporal_engine: Arc<Mutex<Option<TemporalEngine<InMemoryEnvelopeStore>>>>,
|
|
pub command_executor: Arc<Mutex<UCXLCommandExecutor>>,
|
|
}
|
|
|
|
impl AppState {
|
|
pub fn new() -> Self {
|
|
let store = InMemoryEnvelopeStore::new();
|
|
|
|
// Initialize BZZZ Gateway with DHT
|
|
let bzzz_config = BZZZConfig::default();
|
|
let bzzz_gateway = BZZZGateway::new(bzzz_config).unwrap();
|
|
|
|
AppState {
|
|
store: Arc::new(Mutex::new(store)),
|
|
dht_store: Arc::new(Mutex::new(None)),
|
|
bzzz_gateway: Arc::new(Mutex::new(Some(bzzz_gateway))),
|
|
temporal_engine: Arc::new(Mutex::new(None)),
|
|
command_executor: Arc::new(Mutex::new(UCXLCommandExecutor::new())),
|
|
}
|
|
}
|
|
|
|
pub async fn initialize_dht(&self) -> std::result::Result<(), String> {
|
|
// Start BZZZ Gateway
|
|
let mut gateway_clone = {
|
|
let gateway_opt = self.bzzz_gateway.lock().map_err(|e| e.to_string())?;
|
|
gateway_opt.clone()
|
|
};
|
|
|
|
if let Some(ref mut gateway) = gateway_clone.as_mut() {
|
|
gateway.start().await.map_err(|e| e.to_string())?;
|
|
|
|
// Update the state with the started gateway
|
|
if let Ok(mut gateway_opt) = self.bzzz_gateway.lock() {
|
|
*gateway_opt = gateway_clone;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub enum UiToCoreCommand {
|
|
// Basic envelope operations
|
|
GetEnvelope { envelope_id: String },
|
|
GetEnvelopeByUri { uri: String },
|
|
StoreEnvelope { envelope: EnvelopeData },
|
|
DeleteEnvelope { envelope_id: String, soft_delete: bool },
|
|
|
|
// UCXL protocol commands
|
|
ExecuteUCXLGet { uri: String, version: Option<String>, at_time: Option<String> },
|
|
ExecuteUCXLPut { uri: String, content: String, content_type: String, metadata: HashMap<String, serde_json::Value> },
|
|
ExecuteUCXLPost { uri: String, markdown_content: String, author: Option<String>, title: Option<String> },
|
|
ExecuteUCXLAnnounce { uri: String, envelope_id: String, announcement_data: HashMap<String, serde_json::Value> },
|
|
ExecuteUCXLDelete { uri: String, version: Option<String>, soft_delete: bool },
|
|
|
|
// Temporal navigation commands
|
|
GetTemporalState { doc_id: String, version_id: Option<String>, at_time: Option<String>, branch_name: Option<String> },
|
|
GetTimeline { doc_id: String },
|
|
DiffVersions { doc_id: String, from_version: String, to_version: String },
|
|
|
|
// Search commands
|
|
SearchEnvelopes { query: SearchQueryData },
|
|
|
|
// Store statistics
|
|
GetStoreStats,
|
|
|
|
// DHT and BZZZ operations
|
|
InitializeDHT,
|
|
GetNetworkStatus,
|
|
GetBZZZStats,
|
|
StoreToDHT { envelope: EnvelopeData },
|
|
RetrieveFromDHT { envelope_id: String },
|
|
GetPeerList,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct EnvelopeData {
|
|
pub ucxl_uri: String,
|
|
pub content: String,
|
|
pub content_type: String,
|
|
pub author: Option<String>,
|
|
pub title: Option<String>,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct SearchQueryData {
|
|
pub text: Option<String>,
|
|
pub tags: Vec<String>,
|
|
pub author: Option<String>,
|
|
pub content_type: Option<String>,
|
|
pub limit: Option<usize>,
|
|
pub offset: Option<usize>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub enum CoreToUiResponse {
|
|
// Standardized UCXL responses
|
|
Success(UCXLSuccessResponse),
|
|
Error(UCXLErrorResponse),
|
|
|
|
// Legacy responses for backward compatibility
|
|
Envelope(Envelope),
|
|
EnvelopeList(Vec<Envelope>),
|
|
|
|
// UCXL command responses
|
|
UCXLResponse(UCXLResponse),
|
|
|
|
// Temporal responses
|
|
TemporalState(TemporalState),
|
|
Timeline(TimelineView),
|
|
VersionDiff(VersionDiff),
|
|
|
|
// Store info
|
|
StoreStats(StoreStats),
|
|
|
|
// DHT and BZZZ responses
|
|
NetworkStatus(NetworkStatus),
|
|
BZZZStats(BZZZStats),
|
|
PeerList(Vec<BZZZPeer>),
|
|
}
|
|
|
|
// Tauri command handlers
|
|
|
|
#[tauri::command]
|
|
pub async fn handle_command(command: UiToCoreCommand, state: tauri::State<'_, AppState>) -> std::result::Result<CoreToUiResponse, String> {
|
|
match command {
|
|
UiToCoreCommand::GetEnvelope { envelope_id } => {
|
|
// Clone the store for async usage
|
|
let result = {
|
|
let store = match state.store.lock() {
|
|
Ok(store) => store.clone(),
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire store lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
store.retrieve(&envelope_id).await
|
|
};
|
|
|
|
match result {
|
|
Ok(Some(envelope)) => Ok(CoreToUiResponse::Envelope(envelope)),
|
|
Ok(None) => Ok(CoreToUiResponse::Error {
|
|
error: "Envelope not found".to_string(),
|
|
details: Some(envelope_id),
|
|
}),
|
|
Err(e) => Ok(CoreToUiResponse::Error {
|
|
error: e.to_string(),
|
|
details: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
UiToCoreCommand::GetEnvelopeByUri { uri } => {
|
|
let ucxl_uri = match UCXLUri::new(&uri) {
|
|
Ok(uri) => uri,
|
|
Err(_) => return Ok(create_uri_error(&uri, "/envelope/by-uri")),
|
|
};
|
|
|
|
let result = {
|
|
let store = match state.store.lock() {
|
|
Ok(store) => store.clone(),
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire store lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
store.retrieve_by_uri(&ucxl_uri).await
|
|
};
|
|
|
|
match result {
|
|
Ok(Some(envelope)) => Ok(CoreToUiResponse::Envelope(envelope)),
|
|
Ok(None) => Ok(create_envelope_error(UCXLErrorCode::NotFound, &uri, "/envelope/by-uri")),
|
|
Err(e) => Ok(create_error_response(
|
|
UCXLErrorCode::InternalError,
|
|
"/envelope/by-uri",
|
|
None
|
|
)),
|
|
}
|
|
}
|
|
|
|
UiToCoreCommand::StoreEnvelope { envelope } => {
|
|
let ucxl_uri = match UCXLUri::new(&envelope.ucxl_uri) {
|
|
Ok(uri) => uri,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Invalid UCXL URI".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
let metadata = EnvelopeMetadata {
|
|
author: envelope.author,
|
|
title: envelope.title,
|
|
tags: envelope.tags,
|
|
source: Some("ucxl-browser".to_string()),
|
|
context_data: HashMap::new(),
|
|
};
|
|
|
|
let new_envelope = match Envelope::new(ucxl_uri, envelope.content, envelope.content_type, metadata) {
|
|
Ok(env) => env,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to create envelope".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
let envelope_id = new_envelope.id.clone();
|
|
let result = {
|
|
let store = match state.store.lock() {
|
|
Ok(store) => store.clone(),
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire store lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
store.store(&new_envelope).await
|
|
};
|
|
|
|
match result {
|
|
Ok(()) => Ok(CoreToUiResponse::Success {
|
|
message: "Envelope stored successfully".to_string(),
|
|
data: Some(serde_json::json!({ "envelope_id": envelope_id })),
|
|
}),
|
|
Err(e) => Ok(CoreToUiResponse::Error {
|
|
error: e.to_string(),
|
|
details: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
UiToCoreCommand::ExecuteUCXLGet { uri, version, at_time } => {
|
|
let ucxl_uri = match UCXLUri::new(&uri) {
|
|
Ok(uri) => uri,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Invalid UCXL URI".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
let parsed_at_time = if let Some(at_time_str) = at_time {
|
|
match chrono::DateTime::parse_from_rfc3339(&at_time_str) {
|
|
Ok(dt) => Some(dt.with_timezone(&chrono::Utc)),
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Invalid date format".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let get_cmd = GetCommand {
|
|
ucxl_uri,
|
|
version,
|
|
at_time: parsed_at_time,
|
|
};
|
|
|
|
let result = {
|
|
let executor = match state.command_executor.lock() {
|
|
Ok(exec) => exec.clone(),
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire executor lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
executor.execute(UCXLCommand::Get(get_cmd)).await
|
|
};
|
|
|
|
match result {
|
|
Ok(response) => Ok(CoreToUiResponse::UCXLResponse(response)),
|
|
Err(e) => Ok(CoreToUiResponse::Error {
|
|
error: e.to_string(),
|
|
details: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
UiToCoreCommand::ExecuteUCXLPost { uri, markdown_content, author, title } => {
|
|
let ucxl_uri = match UCXLUri::new(&uri) {
|
|
Ok(uri) => uri,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Invalid UCXL URI".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
let doc_id = ucxl_uri.path.clone();
|
|
let markdown_context = MarkdownContext::new(doc_id, markdown_content, author, title);
|
|
|
|
let post_cmd = PostCommand {
|
|
ucxl_uri,
|
|
markdown_context,
|
|
};
|
|
|
|
let result = {
|
|
let executor = match state.command_executor.lock() {
|
|
Ok(exec) => exec.clone(),
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire executor lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
executor.execute(UCXLCommand::Post(post_cmd)).await
|
|
};
|
|
|
|
match result {
|
|
Ok(response) => Ok(CoreToUiResponse::UCXLResponse(response)),
|
|
Err(e) => Ok(CoreToUiResponse::Error {
|
|
error: e.to_string(),
|
|
details: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
UiToCoreCommand::SearchEnvelopes { query } => {
|
|
let search_query = SearchQuery {
|
|
text: query.text,
|
|
tags: query.tags,
|
|
author: query.author,
|
|
content_type: query.content_type,
|
|
date_range: None, // TODO: Add date range support in UI
|
|
limit: query.limit,
|
|
offset: query.offset,
|
|
};
|
|
|
|
let result = {
|
|
let store = match state.store.lock() {
|
|
Ok(store) => store.clone(),
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire store lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
store.search(&search_query).await
|
|
};
|
|
|
|
match result {
|
|
Ok(envelopes) => Ok(CoreToUiResponse::EnvelopeList(envelopes)),
|
|
Err(e) => Ok(CoreToUiResponse::Error {
|
|
error: e.to_string(),
|
|
details: None,
|
|
}),
|
|
}
|
|
}
|
|
|
|
UiToCoreCommand::GetStoreStats => {
|
|
let stats = {
|
|
let store = match state.store.lock() {
|
|
Ok(store) => store,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire store lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
store.get_stats()
|
|
};
|
|
Ok(CoreToUiResponse::StoreStats(stats))
|
|
}
|
|
|
|
UiToCoreCommand::InitializeDHT => {
|
|
match state.initialize_dht().await {
|
|
Ok(()) => Ok(CoreToUiResponse::Success {
|
|
message: "DHT network initialized successfully".to_string(),
|
|
data: None,
|
|
}),
|
|
Err(e) => Ok(CoreToUiResponse::Error {
|
|
error: "Failed to initialize DHT".to_string(),
|
|
details: Some(e),
|
|
}),
|
|
}
|
|
}
|
|
|
|
UiToCoreCommand::GetNetworkStatus => {
|
|
let gateway_clone = {
|
|
let gateway_opt = match state.bzzz_gateway.lock() {
|
|
Ok(gateway) => gateway,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire gateway lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
gateway_opt.clone()
|
|
};
|
|
|
|
let network_status = if let Some(gateway) = gateway_clone {
|
|
gateway.get_network_status().await
|
|
} else {
|
|
return Ok(CoreToUiResponse::Error {
|
|
error: "DHT network not initialized".to_string(),
|
|
details: Some("Call InitializeDHT first".to_string()),
|
|
});
|
|
};
|
|
|
|
Ok(CoreToUiResponse::NetworkStatus(network_status))
|
|
}
|
|
|
|
UiToCoreCommand::GetBZZZStats => {
|
|
let gateway_clone = {
|
|
let gateway_opt = match state.bzzz_gateway.lock() {
|
|
Ok(gateway) => gateway,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire gateway lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
gateway_opt.clone()
|
|
};
|
|
|
|
let bzzz_stats = if let Some(gateway) = gateway_clone {
|
|
gateway.get_gateway_stats()
|
|
} else {
|
|
return Ok(create_error_response(
|
|
UCXLErrorCode::ServiceUnavailable,
|
|
"/bzzz/stats",
|
|
None
|
|
));
|
|
};
|
|
|
|
Ok(CoreToUiResponse::BZZZStats(bzzz_stats))
|
|
}
|
|
|
|
UiToCoreCommand::StoreToDHT { envelope } => {
|
|
let ucxl_uri = match UCXLUri::new(&envelope.ucxl_uri) {
|
|
Ok(uri) => uri,
|
|
Err(_) => return Ok(create_uri_error(&envelope.ucxl_uri, "/dht/store")),
|
|
};
|
|
|
|
let metadata = EnvelopeMetadata {
|
|
author: envelope.author,
|
|
title: envelope.title,
|
|
tags: envelope.tags,
|
|
source: Some("ucxl-browser".to_string()),
|
|
context_data: HashMap::new(),
|
|
};
|
|
|
|
let new_envelope = match Envelope::new(ucxl_uri, envelope.content, envelope.content_type, metadata) {
|
|
Ok(env) => env,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to create envelope".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
let gateway_clone = {
|
|
let gateway_opt = match state.bzzz_gateway.lock() {
|
|
Ok(gateway) => gateway,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire gateway lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
gateway_opt.clone()
|
|
};
|
|
|
|
let envelope_id = if let Some(gateway) = gateway_clone {
|
|
match gateway.store_context(&new_envelope).await {
|
|
Ok(id) => id,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to store to DHT".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
}
|
|
} else {
|
|
return Ok(CoreToUiResponse::Error {
|
|
error: "DHT network not initialized".to_string(),
|
|
details: Some("Call InitializeDHT first".to_string()),
|
|
});
|
|
};
|
|
|
|
Ok(CoreToUiResponse::Success {
|
|
message: "Content stored to DHT successfully".to_string(),
|
|
data: Some(serde_json::json!({ "envelope_id": envelope_id })),
|
|
})
|
|
}
|
|
|
|
UiToCoreCommand::RetrieveFromDHT { envelope_id } => {
|
|
let gateway_clone = {
|
|
let gateway_opt = match state.bzzz_gateway.lock() {
|
|
Ok(gateway) => gateway,
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to acquire gateway lock".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
};
|
|
|
|
gateway_opt.clone()
|
|
};
|
|
|
|
let envelope = if let Some(gateway) = gateway_clone {
|
|
match gateway.retrieve_context(&envelope_id).await {
|
|
Ok(Some(env)) => env,
|
|
Ok(None) => return Ok(CoreToUiResponse::Error {
|
|
error: "Envelope not found in DHT".to_string(),
|
|
details: Some(envelope_id),
|
|
}),
|
|
Err(e) => return Ok(CoreToUiResponse::Error {
|
|
error: "Failed to retrieve from DHT".to_string(),
|
|
details: Some(e.to_string()),
|
|
}),
|
|
}
|
|
} else {
|
|
return Ok(CoreToUiResponse::Error {
|
|
error: "DHT network not initialized".to_string(),
|
|
details: Some("Call InitializeDHT first".to_string()),
|
|
});
|
|
};
|
|
|
|
Ok(CoreToUiResponse::Envelope(envelope))
|
|
}
|
|
|
|
UiToCoreCommand::GetPeerList => {
|
|
// Mock peer list for now
|
|
let peers = vec![
|
|
BZZZPeer {
|
|
peer_id: "bzzz-peer-1".to_string(),
|
|
ip_address: "192.168.1.100".to_string(),
|
|
port: 8080,
|
|
last_seen: chrono::Utc::now(),
|
|
capabilities: PeerCapabilities {
|
|
supports_ucxl: true,
|
|
supports_dht: true,
|
|
supports_temporal: true,
|
|
storage_capacity: 1_000_000_000,
|
|
api_version: "v2.0".to_string(),
|
|
},
|
|
health_score: 0.95,
|
|
latency_ms: Some(15),
|
|
},
|
|
BZZZPeer {
|
|
peer_id: "bzzz-peer-2".to_string(),
|
|
ip_address: "192.168.1.101".to_string(),
|
|
port: 8080,
|
|
last_seen: chrono::Utc::now(),
|
|
capabilities: PeerCapabilities {
|
|
supports_ucxl: true,
|
|
supports_dht: true,
|
|
supports_temporal: false,
|
|
storage_capacity: 500_000_000,
|
|
api_version: "v2.0".to_string(),
|
|
},
|
|
health_score: 0.88,
|
|
latency_ms: Some(22),
|
|
},
|
|
];
|
|
|
|
Ok(CoreToUiResponse::PeerList(peers))
|
|
}
|
|
|
|
// TODO: Implement remaining commands
|
|
_ => Ok(CoreToUiResponse::Error {
|
|
error: "Command not implemented yet".to_string(),
|
|
details: Some(format!("{:?}", command)),
|
|
}),
|
|
}
|
|
}
|