Skip to main content

context_snapshot/
context_snapshot.rs

1//! Generic context snapshot — builds a ScreenContext from caller-provided streams.
2//!
3//! Usage:
4//!   cargo run -p cel-context --example context_snapshot
5//!   cargo run -p cel-context --example context_snapshot -- --json
6
7use std::collections::HashMap;
8
9use cel_context::{
10    Bounds, ContentRole, ContextContribution, ContextElement, ContextMerger, ContextSource,
11    ElementState, StreamStatus,
12};
13
14fn main() {
15    let json_output = std::env::args().any(|arg| arg == "--json");
16
17    let mut merger = ContextMerger::new().with_defaults("Example CRM", "Customer Detail");
18    merger.push(
19        ContextContribution::new(
20            "crm_api",
21            vec![
22                element(
23                    "crm:title",
24                    "heading",
25                    "Customer: Acme Corp",
26                    ContextSource::NativeApi,
27                    None,
28                ),
29                element(
30                    "crm:button:save",
31                    "button",
32                    "Save changes",
33                    ContextSource::NativeApi,
34                    Some(Bounds {
35                        x: 720,
36                        y: 64,
37                        width: 128,
38                        height: 36,
39                    }),
40                ),
41            ],
42        )
43        .with_app("Example CRM")
44        .with_window("Customer Detail")
45        .with_stream_status(StreamStatus {
46            accessibility: false,
47            display: false,
48            network: false,
49            signals: false,
50            vision: false,
51            audio_capture: false,
52        }),
53    );
54    merger.push(ContextContribution::new(
55        "browser_dom",
56        vec![element(
57            "dom:input:notes",
58            "textbox",
59            "Internal notes",
60            ContextSource::Cdp,
61            Some(Bounds {
62                x: 160,
63                y: 420,
64                width: 520,
65                height: 120,
66            }),
67        )],
68    ));
69
70    let ctx = merger.build();
71
72    if json_output {
73        println!("{}", serde_json::to_string_pretty(&ctx).unwrap());
74        return;
75    }
76
77    println!("=== CEL Context Snapshot ===");
78    println!("App:       {}", ctx.app);
79    println!("Window:    {}", ctx.window);
80    println!("Timestamp: {} ms", ctx.timestamp_ms);
81    println!("Elements:  {}", ctx.elements.len());
82    println!();
83
84    for (i, elem) in ctx.elements.iter().enumerate() {
85        let label = elem.label.as_deref().unwrap_or("(no label)");
86        let bounds = elem
87            .bounds
88            .as_ref()
89            .map(|b| format!("[{},{} {}x{}]", b.x, b.y, b.width, b.height))
90            .unwrap_or_else(|| "(no bounds)".into());
91        println!(
92            "  {:>2}. [{:.2}] {:12} {:22} {} {:?}",
93            i + 1,
94            elem.confidence,
95            elem.element_type,
96            label,
97            bounds,
98            elem.source
99        );
100    }
101}
102
103fn element(
104    id: &str,
105    element_type: &str,
106    label: &str,
107    source: ContextSource,
108    bounds: Option<Bounds>,
109) -> ContextElement {
110    ContextElement {
111        id: id.into(),
112        label: Some(label.into()),
113        description: None,
114        element_type: element_type.into(),
115        value: None,
116        bounds,
117        state: ElementState::default(),
118        parent_id: None,
119        actions: Vec::new(),
120        confidence: 0.0,
121        source,
122        content_role: ContentRole::Interactive,
123        properties: HashMap::new(),
124    }
125}