widget_intelligence/
lib.rs

1//! # Widget Intelligence
2//!
3//! A Rust library for intelligent widget suggestion and learning based on user behavior patterns.
4//!
5//! This crate provides functionality for:
6//! - Learning from widget usage patterns
7//! - Suggesting widget values based on similarity
8//! - Persistent storage using Sled database
9//! - Integration with Tauri applications
10//! - Extracting widget information from Kyma JSON data
11//!
12//! ## Features
13//!
14//! - **Similarity Engine**: Core algorithm for finding similar widgets based on multiple features
15//! - **Persistence**: Sled-based storage for long-term learning
16//! - **Kyma Integration**: Extract widget data from Kyma JSON format
17//! - **Tauri Commands**: Ready-to-use Tauri commands for frontend integration
18//!
19//! ## Example
20//!
21//! ```rust
22//! use widget_intelligence::{WidgetSuggestionEngine, Widget};
23//!
24//! let mut engine = WidgetSuggestionEngine::new();
25//!
26//! let widget = Widget {
27//!     label: Some("Master Volume".to_string()),
28//!     minimum: Some(0.0),
29//!     maximum: Some(127.0),
30//!     current_value: Some(95.0),
31//!     is_generated: Some(false),
32//!     display_type: Some("slider".to_string()),
33//! };
34//!
35//! engine.store_widget(widget);
36//!
37//! let suggestions = engine.get_suggestions(&Widget {
38//!     label: Some("Volume".to_string()),
39//!     ..Default::default()
40//! }, 5);
41//! ```
42
43pub mod kyma_extractor;
44pub mod persistence;
45pub mod similarity_engine;
46pub mod tauri_examples;
47
48// Re-export main types for convenience
49pub use similarity_engine::{
50    FilteredWidgetDescription, Preset, Suggestion, ValueStats, Widget, WidgetFeatures,
51    WidgetRecord, WidgetSuggestionEngine, WidgetValue,
52};
53
54pub use persistence::{
55    ExportData, PersistentWidgetSuggestionEngine, SledPersistenceError, SledPersistenceManager,
56};
57
58pub use kyma_extractor::{KymaWidgetExtractor, WidgetMetadata};
59
60pub use tauri_examples::{
61    IntelligenceStats, PresetData, StandaloneIntelligenceService, SuggestionResponse,
62    WidgetInsightResponse,
63};
64
65impl Default for Widget {
66    fn default() -> Self {
67        Self {
68            label: None,
69            minimum: None,
70            maximum: None,
71            current_value: None,
72            is_generated: None,
73            display_type: None,
74            event_id: None,
75            values: Vec::new(),
76        }
77    }
78}
79
80/// Initialize the widget intelligence system with a database path
81pub fn init_intelligence_system<P: AsRef<std::path::Path>>(
82    db_path: P,
83) -> Result<PersistentWidgetSuggestionEngine, persistence::SledPersistenceError> {
84    PersistentWidgetSuggestionEngine::new(db_path)
85}
86
87/// Initialize the standalone intelligence service
88pub fn init_standalone_service(db_path: &str) -> Result<StandaloneIntelligenceService, String> {
89    StandaloneIntelligenceService::new(db_path)
90}
91
92/// Utility function to validate widget data
93pub fn validate_widget(widget: &Widget) -> Result<(), String> {
94    if let (Some(min), Some(max)) = (widget.minimum, widget.maximum) {
95        if min >= max {
96            return Err("Minimum value must be less than maximum value".to_string());
97        }
98
99        if let Some(current) = widget.current_value {
100            if current < min || current > max {
101                return Err("Current value must be within minimum and maximum bounds".to_string());
102            }
103        }
104    }
105
106    Ok(())
107}
108
109/// Utility function to create a simple widget for testing
110pub fn create_test_widget(label: &str, min: f64, max: f64, current: f64) -> Widget {
111    Widget {
112        label: Some(label.to_string()),
113        minimum: Some(min),
114        maximum: Some(max),
115        current_value: Some(current),
116        is_generated: Some(false),
117        display_type: Some("slider".to_string()),
118        event_id: None,
119        values: vec![current],
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use std::{thread, time::Duration};
127    use tempfile::tempdir;
128
129    #[test]
130    fn test_persistent_system() -> Result<(), Box<dyn std::error::Error>> {
131        let temp_dir = tempdir()?;
132        let db_path = temp_dir.path().join("test_persistent_lib");
133
134        // Add a small delay to ensure any previous test's file handles are released
135        thread::sleep(Duration::from_millis(100));
136
137        let mut system = init_intelligence_system(&db_path)?;
138
139        let widget = create_test_widget("Test Widget", 0.0, 100.0, 50.0);
140        system.store_widget(widget)?;
141
142        let stats = system.get_stats();
143        assert_eq!(stats.get("total_widgets"), Some(&1));
144
145        // Ensure data is flushed and file handles are released
146        system.flush()?;
147        drop(system);
148
149        thread::sleep(Duration::from_millis(100));
150
151        let system2 = init_intelligence_system(&db_path)?;
152        let stats2 = system2.get_stats();
153        assert_eq!(stats2.get("total_widgets"), Some(&1));
154
155        // Clean up
156        drop(system2);
157        temp_dir.close()?;
158
159        Ok(())
160    }
161}