ruvector_tiny_dancer_wasm/
lib.rs1use wasm_bindgen::prelude::*;
4use ruvector_tiny_dancer_core::{
5 Router as CoreRouter,
6 types::{
7 RouterConfig as CoreRouterConfig,
8 RoutingRequest as CoreRoutingRequest,
9 RoutingResponse as CoreRoutingResponse,
10 Candidate as CoreCandidate,
11 },
12};
13use std::collections::HashMap;
14
15#[wasm_bindgen(start)]
17pub fn init() {
18 #[cfg(feature = "console_error_panic_hook")]
19 console_error_panic_hook::set_once();
20}
21
22#[wasm_bindgen]
24#[derive(Clone)]
25pub struct RouterConfig {
26 model_path: String,
27 confidence_threshold: f32,
28 max_uncertainty: f32,
29 enable_circuit_breaker: bool,
30 circuit_breaker_threshold: u32,
31 enable_quantization: bool,
32}
33
34#[wasm_bindgen]
35impl RouterConfig {
36 #[wasm_bindgen(constructor)]
37 pub fn new() -> Self {
38 Self {
39 model_path: "./models/fastgrnn.safetensors".to_string(),
40 confidence_threshold: 0.85,
41 max_uncertainty: 0.15,
42 enable_circuit_breaker: true,
43 circuit_breaker_threshold: 5,
44 enable_quantization: true,
45 }
46 }
47
48 #[wasm_bindgen(setter)]
49 pub fn set_model_path(&mut self, path: String) {
50 self.model_path = path;
51 }
52
53 #[wasm_bindgen(setter)]
54 pub fn set_confidence_threshold(&mut self, threshold: f32) {
55 self.confidence_threshold = threshold;
56 }
57
58 #[wasm_bindgen(setter)]
59 pub fn set_max_uncertainty(&mut self, uncertainty: f32) {
60 self.max_uncertainty = uncertainty;
61 }
62}
63
64impl From<RouterConfig> for CoreRouterConfig {
65 fn from(config: RouterConfig) -> Self {
66 CoreRouterConfig {
67 model_path: config.model_path,
68 confidence_threshold: config.confidence_threshold,
69 max_uncertainty: config.max_uncertainty,
70 enable_circuit_breaker: config.enable_circuit_breaker,
71 circuit_breaker_threshold: config.circuit_breaker_threshold,
72 enable_quantization: config.enable_quantization,
73 database_path: None,
74 }
75 }
76}
77
78#[wasm_bindgen]
80pub struct Candidate {
81 id: String,
82 embedding: Vec<f32>,
83 metadata: String,
84 created_at: i64,
85 access_count: u64,
86 success_rate: f32,
87}
88
89#[wasm_bindgen]
90impl Candidate {
91 #[wasm_bindgen(constructor)]
92 pub fn new(
93 id: String,
94 embedding: Vec<f32>,
95 metadata: String,
96 created_at: i64,
97 access_count: u64,
98 success_rate: f32,
99 ) -> Self {
100 Self {
101 id,
102 embedding,
103 metadata,
104 created_at,
105 access_count,
106 success_rate,
107 }
108 }
109}
110
111impl TryFrom<Candidate> for CoreCandidate {
112 type Error = JsValue;
113
114 fn try_from(candidate: Candidate) -> Result<Self, Self::Error> {
115 let metadata: HashMap<String, serde_json::Value> =
116 serde_json::from_str(&candidate.metadata)
117 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?;
118
119 Ok(CoreCandidate {
120 id: candidate.id,
121 embedding: candidate.embedding,
122 metadata,
123 created_at: candidate.created_at,
124 access_count: candidate.access_count,
125 success_rate: candidate.success_rate,
126 })
127 }
128}
129
130#[wasm_bindgen]
132pub struct RoutingRequest {
133 query_embedding: Vec<f32>,
134 candidates: Vec<Candidate>,
135 metadata: Option<String>,
136}
137
138#[wasm_bindgen]
139impl RoutingRequest {
140 #[wasm_bindgen(constructor)]
141 pub fn new(query_embedding: Vec<f32>, candidates: Vec<Candidate>) -> Self {
142 Self {
143 query_embedding,
144 candidates,
145 metadata: None,
146 }
147 }
148
149 #[wasm_bindgen(setter)]
150 pub fn set_metadata(&mut self, metadata: String) {
151 self.metadata = Some(metadata);
152 }
153}
154
155impl TryFrom<RoutingRequest> for CoreRoutingRequest {
156 type Error = JsValue;
157
158 fn try_from(request: RoutingRequest) -> Result<Self, Self::Error> {
159 let candidates: Result<Vec<CoreCandidate>, JsValue> = request
160 .candidates
161 .into_iter()
162 .map(|c| c.try_into())
163 .collect();
164
165 let metadata = if let Some(meta_str) = request.metadata {
166 Some(serde_json::from_str(&meta_str)
167 .map_err(|e| JsValue::from_str(&format!("Invalid metadata: {}", e)))?)
168 } else {
169 None
170 };
171
172 Ok(CoreRoutingRequest {
173 query_embedding: request.query_embedding,
174 candidates: candidates?,
175 metadata,
176 })
177 }
178}
179
180#[wasm_bindgen]
182pub struct RoutingResponse {
183 decisions_json: String,
184 inference_time_us: u64,
185 candidates_processed: usize,
186 feature_time_us: u64,
187}
188
189#[wasm_bindgen]
190impl RoutingResponse {
191 #[wasm_bindgen(getter)]
192 pub fn decisions_json(&self) -> String {
193 self.decisions_json.clone()
194 }
195
196 #[wasm_bindgen(getter)]
197 pub fn inference_time_us(&self) -> u64 {
198 self.inference_time_us
199 }
200
201 #[wasm_bindgen(getter)]
202 pub fn candidates_processed(&self) -> usize {
203 self.candidates_processed
204 }
205
206 #[wasm_bindgen(getter)]
207 pub fn feature_time_us(&self) -> u64 {
208 self.feature_time_us
209 }
210}
211
212impl From<CoreRoutingResponse> for RoutingResponse {
213 fn from(response: CoreRoutingResponse) -> Self {
214 let decisions_json = serde_json::to_string(&response.decisions).unwrap_or_default();
215
216 Self {
217 decisions_json,
218 inference_time_us: response.inference_time_us,
219 candidates_processed: response.candidates_processed,
220 feature_time_us: response.feature_time_us,
221 }
222 }
223}
224
225#[wasm_bindgen]
227pub struct Router {
228 inner: CoreRouter,
229}
230
231#[wasm_bindgen]
232impl Router {
233 #[wasm_bindgen(constructor)]
235 pub fn new(config: RouterConfig) -> Result<Router, JsValue> {
236 let core_config: CoreRouterConfig = config.into();
237 let router = CoreRouter::new(core_config)
238 .map_err(|e| JsValue::from_str(&format!("Failed to create router: {}", e)))?;
239
240 Ok(Router { inner: router })
241 }
242
243 pub fn route(&self, request: RoutingRequest) -> Result<RoutingResponse, JsValue> {
245 let core_request: CoreRoutingRequest = request.try_into()?;
246 let core_response = self
247 .inner
248 .route(core_request)
249 .map_err(|e| JsValue::from_str(&format!("Routing failed: {}", e)))?;
250
251 Ok(core_response.into())
252 }
253
254 pub fn circuit_breaker_status(&self) -> Option<bool> {
256 self.inner.circuit_breaker_status()
257 }
258}
259
260#[wasm_bindgen]
262pub fn version() -> String {
263 env!("CARGO_PKG_VERSION").to_string()
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_version() {
272 assert!(!version().is_empty());
273 }
274}