v0.1.0-pre.052
This commit is contained in:
267
kb-app-demo-desktop/src/demo_core_extraction.rs
Normal file
267
kb-app-demo-desktop/src/demo_core_extraction.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
// file: kb-app-demo-desktop/src/demo_core_extraction.rs
|
||||
// version: 9
|
||||
|
||||
//! Tauri commands and UI payloads for canonical transaction to core extraction.
|
||||
|
||||
use tauri::Emitter; // rust-rules: trait-import
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Initial options shown by the core extraction demo.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionOptionsPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoCoreExtractionOptionsPayload {
|
||||
/// Current extractor implementation version.
|
||||
pub(crate) processor_version: std::string::String,
|
||||
/// Default maximum selected canonical transactions.
|
||||
pub(crate) default_limit: u32,
|
||||
/// Default maximum concurrent extractions.
|
||||
pub(crate) default_max_concurrent_extractions: u32,
|
||||
/// Whether one extraction campaign is currently running.
|
||||
pub(crate) running: bool,
|
||||
}
|
||||
|
||||
/// UI request for one bounded core extraction campaign.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionRequest.ts"
|
||||
)]
|
||||
pub(crate) struct DemoCoreExtractionRequest {
|
||||
/// Source mode: signatures, pending or slot_range.
|
||||
pub(crate) mode: std::string::String,
|
||||
/// Newline-separated signatures for exact selection.
|
||||
pub(crate) signatures_text: std::option::Option<std::string::String>,
|
||||
/// Optional program id already present in core instructions.
|
||||
pub(crate) program_id: std::option::Option<std::string::String>,
|
||||
/// Optional inclusive minimum slot.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) min_slot: std::option::Option<u64>,
|
||||
/// Optional inclusive maximum slot.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) max_slot: std::option::Option<u64>,
|
||||
/// Maximum selected canonical transactions.
|
||||
pub(crate) limit: u32,
|
||||
/// Maximum concurrent extraction operations.
|
||||
pub(crate) max_concurrent_extractions: u32,
|
||||
/// Forces replacement of already current core rows.
|
||||
pub(crate) force_replay: bool,
|
||||
}
|
||||
|
||||
/// One progress event emitted to the core extraction window.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionProgressPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoCoreExtractionProgressPayload {
|
||||
/// RFC 3339 timestamp.
|
||||
pub(crate) timestamp: std::string::String,
|
||||
/// Stable severity code.
|
||||
pub(crate) level: std::string::String,
|
||||
/// Human-readable message.
|
||||
pub(crate) message: std::string::String,
|
||||
/// Number of terminal candidates.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) completed: u64,
|
||||
/// Total selected candidates.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) total: u64,
|
||||
}
|
||||
|
||||
/// Final UI-safe summary for one extraction campaign.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionSummaryPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoCoreExtractionSummaryPayload {
|
||||
/// Current extractor implementation version.
|
||||
pub(crate) processor_version: std::string::String,
|
||||
/// Number of selected canonical transactions.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) selected: u64,
|
||||
/// Number admitted to the bounded execution queue.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) started: u64,
|
||||
/// Number reaching a terminal result.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) completed: u64,
|
||||
/// Number skipped by version/hash idempotence.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) skipped: u64,
|
||||
/// Number extracted and committed.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) extracted: u64,
|
||||
/// Number failed.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) failed: u64,
|
||||
/// Number admitted but cancelled before a terminal result.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) cancelled_candidates: u64,
|
||||
/// Number selected but not started after cancellation.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) not_started: u64,
|
||||
/// Whether the campaign was cancelled.
|
||||
pub(crate) cancelled: bool,
|
||||
/// Campaign start timestamp.
|
||||
pub(crate) started_at: std::string::String,
|
||||
/// Campaign finish timestamp.
|
||||
pub(crate) finished_at: std::string::String,
|
||||
}
|
||||
|
||||
pub(crate) struct DemoCoreExtractionObserver<'a> {
|
||||
pub(crate) app_handle: tauri::AppHandle,
|
||||
pub(crate) cancel_requested: &'a std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
impl kb_pipeline::CoreExtractionObserver for crate::DemoCoreExtractionObserver<'_> {
|
||||
fn on_progress(&self, event: &kb_pipeline::CoreExtractionProgressEvent) {
|
||||
let payload = crate::DemoCoreExtractionProgressPayload {
|
||||
timestamp: event.timestamp.clone(),
|
||||
level: event.level.code().to_string(),
|
||||
message: event.message.clone(),
|
||||
completed: event.completed,
|
||||
total: event.total,
|
||||
};
|
||||
let emit_result = self.app_handle.emit_to(
|
||||
"demo_core_extraction",
|
||||
"demo-core-extraction-progress",
|
||||
payload,
|
||||
);
|
||||
if let std::result::Result::Err(error) = emit_result {
|
||||
tracing::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
"cannot emit core extraction progress: {error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_cancelled(&self) -> bool {
|
||||
return self.cancel_requested.load(std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DemoCoreExtractionRunGuard<'a> {
|
||||
pub(crate) running: &'a std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
impl std::ops::Drop for crate::DemoCoreExtractionRunGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.running.store(false, std::sync::atomic::Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_demo_core_extraction_pipeline_request(
|
||||
request: crate::DemoCoreExtractionRequest,
|
||||
) -> std::result::Result<kb_pipeline::CoreExtractionRequest, std::string::String> {
|
||||
let source_result = match request.mode.trim() {
|
||||
"signatures" => {
|
||||
let signatures = split_signatures(request.signatures_text.as_deref());
|
||||
std::result::Result::Ok(kb_pipeline::CoreExtractionSource::Signatures(signatures))
|
||||
},
|
||||
"pending" => std::result::Result::Ok(kb_pipeline::CoreExtractionSource::Pending),
|
||||
"program_id" => {
|
||||
let program_id = match request.program_id {
|
||||
std::option::Option::Some(value) if !value.trim().is_empty() => value,
|
||||
_ => {
|
||||
return std::result::Result::Err(
|
||||
"program id is required for program extraction".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
std::result::Result::Ok(kb_pipeline::CoreExtractionSource::ProgramId { program_id })
|
||||
},
|
||||
"slot_range" => {
|
||||
let min_slot = match request.min_slot {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
"minimum slot is required for slot range extraction".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let max_slot = match request.max_slot {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
"maximum slot is required for slot range extraction".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
std::result::Result::Ok(kb_pipeline::CoreExtractionSource::SlotRange {
|
||||
min_slot,
|
||||
max_slot,
|
||||
})
|
||||
},
|
||||
_ => std::result::Result::Err("unsupported core extraction mode".to_string()),
|
||||
};
|
||||
let source = match source_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let pipeline_request = kb_pipeline::CoreExtractionRequest {
|
||||
source,
|
||||
limit: request.limit,
|
||||
max_concurrent_extractions: request.max_concurrent_extractions,
|
||||
force_replay: request.force_replay,
|
||||
};
|
||||
let validation_result = pipeline_request.validate();
|
||||
if let std::result::Result::Err(error) = validation_result {
|
||||
return std::result::Result::Err(error.to_string());
|
||||
}
|
||||
return std::result::Result::Ok(pipeline_request);
|
||||
}
|
||||
|
||||
fn split_signatures(text: std::option::Option<&str>) -> std::vec::Vec<std::string::String> {
|
||||
let source = match text {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => "",
|
||||
};
|
||||
let mut unique = std::collections::BTreeSet::<std::string::String>::new();
|
||||
let mut output = std::vec::Vec::new();
|
||||
for line in source.lines() {
|
||||
let signature = line.trim();
|
||||
if signature.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if unique.insert(signature.to_string()) {
|
||||
output.push(signature.to_string());
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
pub(crate) fn demo_core_extraction_summary_payload(
|
||||
summary: kb_pipeline::CoreExtractionSummary,
|
||||
) -> crate::DemoCoreExtractionSummaryPayload {
|
||||
return crate::DemoCoreExtractionSummaryPayload {
|
||||
processor_version: summary.processor_version,
|
||||
selected: summary.selected,
|
||||
started: summary.started,
|
||||
completed: summary.completed,
|
||||
skipped: summary.skipped,
|
||||
extracted: summary.extracted,
|
||||
failed: summary.failed,
|
||||
cancelled_candidates: summary.cancelled_candidates,
|
||||
not_started: summary.not_started,
|
||||
cancelled: summary.cancelled,
|
||||
started_at: summary.started_at,
|
||||
finished_at: summary.finished_at,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn signature_source_splits_deduplicates_and_ignores_empty_rows() {
|
||||
let values = super::split_signatures(std::option::Option::Some("alpha\n\n beta \nalpha\n"));
|
||||
assert_eq!(values, std::vec!["alpha".to_string(), "beta".to_string()]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user