1use crate::error::{VectorizerError, Result};
4use crate::models::*;
5use crate::models::hybrid_search::{HybridSearchRequest, HybridSearchResponse, HybridScoringAlgorithm};
6use crate::transport::{Transport, Protocol};
7use crate::http_transport::HttpTransport;
8
9#[cfg(feature = "umicp")]
10use crate::umicp_transport::UmicpTransport;
11
12use serde_json;
13use std::sync::Arc;
14
15pub struct ClientConfig {
17 pub base_url: Option<String>,
19 pub connection_string: Option<String>,
21 pub protocol: Option<Protocol>,
23 pub api_key: Option<String>,
25 pub timeout_secs: Option<u64>,
27 #[cfg(feature = "umicp")]
29 pub umicp: Option<UmicpConfig>,
30}
31
32#[cfg(feature = "umicp")]
33pub struct UmicpConfig {
35 pub host: String,
36 pub port: u16,
37}
38
39impl Default for ClientConfig {
40 fn default() -> Self {
41 Self {
42 base_url: Some("http://localhost:15002".to_string()),
43 connection_string: None,
44 protocol: None,
45 api_key: None,
46 timeout_secs: Some(30),
47 #[cfg(feature = "umicp")]
48 umicp: None,
49 }
50 }
51}
52
53pub struct VectorizerClient {
55 transport: Arc<dyn Transport>,
56 protocol: Protocol,
57 base_url: String,
58}
59
60impl VectorizerClient {
61 pub fn base_url(&self) -> &str {
63 &self.base_url
64 }
65
66 pub fn new(config: ClientConfig) -> Result<Self> {
68 let timeout_secs = config.timeout_secs.unwrap_or(30);
69
70 let (transport, protocol, base_url): (Arc<dyn Transport>, Protocol, String) = if let Some(conn_str) = config.connection_string {
72 let (proto, host, port) = crate::transport::parse_connection_string(&conn_str)?;
74
75 match proto {
76 Protocol::Http => {
77 let transport = HttpTransport::new(&host, config.api_key.as_deref(), timeout_secs)?;
78 (Arc::new(transport), Protocol::Http, host.clone())
79 },
80 #[cfg(feature = "umicp")]
81 Protocol::Umicp => {
82 let port = port.unwrap_or(15003);
83 let transport = UmicpTransport::new(&host, port, config.api_key.as_deref(), timeout_secs)?;
84 let base_url = format!("umicp://{}:{}", host, port);
85 (Arc::new(transport), Protocol::Umicp, base_url)
86 },
87 }
88 } else {
89 let proto = config.protocol.unwrap_or(Protocol::Http);
91
92 match proto {
93 Protocol::Http => {
94 let base_url = config.base_url.unwrap_or_else(|| "http://localhost:15002".to_string());
95 let transport = HttpTransport::new(&base_url, config.api_key.as_deref(), timeout_secs)?;
96 (Arc::new(transport), Protocol::Http, base_url.clone())
97 },
98 #[cfg(feature = "umicp")]
99 Protocol::Umicp => {
100 #[cfg(feature = "umicp")]
101 {
102 let umicp_config = config.umicp.ok_or_else(|| {
103 VectorizerError::configuration("UMICP configuration is required when using UMICP protocol")
104 })?;
105
106 let transport = UmicpTransport::new(
107 &umicp_config.host,
108 umicp_config.port,
109 config.api_key.as_deref(),
110 timeout_secs,
111 )?;
112 let base_url = format!("umicp://{}:{}", umicp_config.host, umicp_config.port);
113 (Arc::new(transport), Protocol::Umicp, base_url)
114 }
115 #[cfg(not(feature = "umicp"))]
116 {
117 return Err(VectorizerError::configuration(
118 "UMICP feature is not enabled. Enable it with --features umicp"
119 ));
120 }
121 },
122 }
123 };
124
125 Ok(Self { transport, protocol, base_url })
126 }
127
128 pub fn new_default() -> Result<Self> {
130 Self::new(ClientConfig::default())
131 }
132
133 pub fn new_with_url(base_url: &str) -> Result<Self> {
135 Self::new(ClientConfig {
136 base_url: Some(base_url.to_string()),
137 ..Default::default()
138 })
139 }
140
141 pub fn new_with_api_key(base_url: &str, api_key: &str) -> Result<Self> {
143 Self::new(ClientConfig {
144 base_url: Some(base_url.to_string()),
145 api_key: Some(api_key.to_string()),
146 ..Default::default()
147 })
148 }
149
150 pub fn from_connection_string(connection_string: &str, api_key: Option<&str>) -> Result<Self> {
152 Self::new(ClientConfig {
153 connection_string: Some(connection_string.to_string()),
154 api_key: api_key.map(|s| s.to_string()),
155 ..Default::default()
156 })
157 }
158
159 pub fn protocol(&self) -> Protocol {
161 self.protocol
162 }
163
164 pub async fn health_check(&self) -> Result<HealthStatus> {
166 let response = self.make_request("GET", "/health", None).await?;
167 let health: HealthStatus = serde_json::from_str(&response)
168 .map_err(|e| VectorizerError::server(format!("Failed to parse health check response: {}", e)))?;
169 Ok(health)
170 }
171
172 pub async fn list_collections(&self) -> Result<Vec<CollectionInfo>> {
174 let response = self.make_request("GET", "/collections", None).await?;
175 let collections_response: CollectionsResponse = serde_json::from_str(&response)
176 .map_err(|e| VectorizerError::server(format!("Failed to parse collections response: {}", e)))?;
177 Ok(collections_response.collections)
178 }
179
180 pub async fn search_vectors(
182 &self,
183 collection: &str,
184 query: &str,
185 limit: Option<usize>,
186 score_threshold: Option<f32>,
187 ) -> Result<SearchResponse> {
188 let mut payload = serde_json::Map::new();
189 payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
190 payload.insert("limit".to_string(), serde_json::Value::Number(limit.unwrap_or(10).into()));
191
192 if let Some(threshold) = score_threshold {
193 payload.insert("score_threshold".to_string(), serde_json::Value::Number(serde_json::Number::from_f64(threshold as f64).unwrap()));
194 }
195
196 let response = self.make_request("POST", &format!("/collections/{}/search/text", collection), Some(serde_json::Value::Object(payload))).await?;
197 let search_response: SearchResponse = serde_json::from_str(&response)
198 .map_err(|e| VectorizerError::server(format!("Failed to parse search response: {}", e)))?;
199 Ok(search_response)
200 }
201
202 pub async fn intelligent_search(&self, request: IntelligentSearchRequest) -> Result<IntelligentSearchResponse> {
206 let response = self.make_request("POST", "/intelligent_search", Some(serde_json::to_value(request).unwrap())).await?;
207 let search_response: IntelligentSearchResponse = serde_json::from_str(&response)
208 .map_err(|e| VectorizerError::server(format!("Failed to parse intelligent search response: {}", e)))?;
209 Ok(search_response)
210 }
211
212 pub async fn semantic_search(&self, request: SemanticSearchRequest) -> Result<SemanticSearchResponse> {
214 let response = self.make_request("POST", "/semantic_search", Some(serde_json::to_value(request).unwrap())).await?;
215 let search_response: SemanticSearchResponse = serde_json::from_str(&response)
216 .map_err(|e| VectorizerError::server(format!("Failed to parse semantic search response: {}", e)))?;
217 Ok(search_response)
218 }
219
220 pub async fn contextual_search(&self, request: ContextualSearchRequest) -> Result<ContextualSearchResponse> {
222 let response = self.make_request("POST", "/contextual_search", Some(serde_json::to_value(request).unwrap())).await?;
223 let search_response: ContextualSearchResponse = serde_json::from_str(&response)
224 .map_err(|e| VectorizerError::server(format!("Failed to parse contextual search response: {}", e)))?;
225 Ok(search_response)
226 }
227
228 pub async fn multi_collection_search(&self, request: MultiCollectionSearchRequest) -> Result<MultiCollectionSearchResponse> {
230 let response = self.make_request("POST", "/multi_collection_search", Some(serde_json::to_value(request).unwrap())).await?;
231 let search_response: MultiCollectionSearchResponse = serde_json::from_str(&response)
232 .map_err(|e| VectorizerError::server(format!("Failed to parse multi-collection search response: {}", e)))?;
233 Ok(search_response)
234 }
235
236 pub async fn hybrid_search(&self, request: HybridSearchRequest) -> Result<HybridSearchResponse> {
238 let url = format!("/collections/{}/hybrid_search", request.collection);
239 let payload = serde_json::json!({
240 "query": request.query,
241 "alpha": request.alpha,
242 "algorithm": match request.algorithm {
243 HybridScoringAlgorithm::ReciprocalRankFusion => "rrf",
244 HybridScoringAlgorithm::WeightedCombination => "weighted",
245 HybridScoringAlgorithm::AlphaBlending => "alpha",
246 },
247 "dense_k": request.dense_k,
248 "sparse_k": request.sparse_k,
249 "final_k": request.final_k,
250 "query_sparse": request.query_sparse.as_ref().map(|sv| serde_json::json!({
251 "indices": sv.indices,
252 "values": sv.values,
253 })),
254 });
255 let response = self.make_request("POST", &url, Some(payload)).await?;
256 let search_response: HybridSearchResponse = serde_json::from_str(&response)
257 .map_err(|e| VectorizerError::server(format!("Failed to parse hybrid search response: {}", e)))?;
258 Ok(search_response)
259 }
260
261 pub async fn qdrant_list_collections(&self) -> Result<serde_json::Value> {
265 let response = self.make_request("GET", "/qdrant/collections", None).await?;
266 let result: serde_json::Value = serde_json::from_str(&response)
267 .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant collections response: {}", e)))?;
268 Ok(result)
269 }
270
271 pub async fn qdrant_get_collection(&self, name: &str) -> Result<serde_json::Value> {
273 let url = format!("/qdrant/collections/{}", name);
274 let response = self.make_request("GET", &url, None).await?;
275 let result: serde_json::Value = serde_json::from_str(&response)
276 .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant collection response: {}", e)))?;
277 Ok(result)
278 }
279
280 pub async fn qdrant_create_collection(&self, name: &str, config: &serde_json::Value) -> Result<serde_json::Value> {
282 let url = format!("/qdrant/collections/{}", name);
283 let payload = serde_json::json!({ "config": config });
284 let response = self.make_request("PUT", &url, Some(payload)).await?;
285 let result: serde_json::Value = serde_json::from_str(&response)
286 .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant create collection response: {}", e)))?;
287 Ok(result)
288 }
289
290 pub async fn qdrant_upsert_points(&self, collection: &str, points: &serde_json::Value, wait: bool) -> Result<serde_json::Value> {
292 let url = format!("/qdrant/collections/{}/points", collection);
293 let payload = serde_json::json!({
294 "points": points,
295 "wait": wait,
296 });
297 let response = self.make_request("PUT", &url, Some(payload)).await?;
298 let result: serde_json::Value = serde_json::from_str(&response)
299 .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant upsert points response: {}", e)))?;
300 Ok(result)
301 }
302
303 pub async fn qdrant_search_points(
305 &self,
306 collection: &str,
307 vector: &[f32],
308 limit: Option<usize>,
309 filter: Option<&serde_json::Value>,
310 with_payload: bool,
311 with_vector: bool,
312 ) -> Result<serde_json::Value> {
313 let url = format!("/qdrant/collections/{}/points/search", collection);
314 let mut payload = serde_json::json!({
315 "vector": vector,
316 "limit": limit.unwrap_or(10),
317 "with_payload": with_payload,
318 "with_vector": with_vector,
319 });
320 if let Some(filter) = filter {
321 payload["filter"] = filter.clone();
322 }
323 let response = self.make_request("POST", &url, Some(payload)).await?;
324 let result: serde_json::Value = serde_json::from_str(&response)
325 .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant search response: {}", e)))?;
326 Ok(result)
327 }
328
329 pub async fn qdrant_delete_points(&self, collection: &str, point_ids: &[serde_json::Value], wait: bool) -> Result<serde_json::Value> {
331 let url = format!("/qdrant/collections/{}/points/delete", collection);
332 let payload = serde_json::json!({
333 "points": point_ids,
334 "wait": wait,
335 });
336 let response = self.make_request("POST", &url, Some(payload)).await?;
337 let result: serde_json::Value = serde_json::from_str(&response)
338 .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant delete points response: {}", e)))?;
339 Ok(result)
340 }
341
342 pub async fn qdrant_retrieve_points(
344 &self,
345 collection: &str,
346 point_ids: &[serde_json::Value],
347 with_payload: bool,
348 with_vector: bool,
349 ) -> Result<serde_json::Value> {
350 let ids_str = point_ids.iter()
351 .map(|id| match id {
352 serde_json::Value::String(s) => s.clone(),
353 serde_json::Value::Number(n) => n.to_string(),
354 _ => serde_json::to_string(id).unwrap_or_default(),
355 })
356 .collect::<Vec<_>>()
357 .join(",");
358 let url = format!(
359 "/qdrant/collections/{}/points?ids={}&with_payload={}&with_vector={}",
360 collection, ids_str, with_payload, with_vector
361 );
362 let response = self.make_request("GET", &url, None).await?;
363 let result: serde_json::Value = serde_json::from_str(&response)
364 .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant retrieve points response: {}", e)))?;
365 Ok(result)
366 }
367
368 pub async fn qdrant_count_points(&self, collection: &str, filter: Option<&serde_json::Value>) -> Result<serde_json::Value> {
370 let url = format!("/qdrant/collections/{}/points/count", collection);
371 let payload = if let Some(filter) = filter {
372 serde_json::json!({ "filter": filter })
373 } else {
374 serde_json::json!({})
375 };
376 let response = self.make_request("POST", &url, Some(payload)).await?;
377 let result: serde_json::Value = serde_json::from_str(&response)
378 .map_err(|e| VectorizerError::server(format!("Failed to parse Qdrant count points response: {}", e)))?;
379 Ok(result)
380 }
381
382 pub async fn create_collection(
384 &self,
385 name: &str,
386 dimension: usize,
387 metric: Option<SimilarityMetric>,
388 ) -> Result<CollectionInfo> {
389 let mut payload = serde_json::Map::new();
390 payload.insert("name".to_string(), serde_json::Value::String(name.to_string()));
391 payload.insert("dimension".to_string(), serde_json::Value::Number(dimension.into()));
392 payload.insert("metric".to_string(), serde_json::Value::String(format!("{:?}", metric.unwrap_or_default()).to_lowercase()));
393
394 let response = self.make_request("POST", "/collections", Some(serde_json::Value::Object(payload))).await?;
395 let create_response: CreateCollectionResponse = serde_json::from_str(&response)
396 .map_err(|e| VectorizerError::server(format!("Failed to parse create collection response: {}", e)))?;
397
398 let info = CollectionInfo {
400 name: create_response.collection,
401 dimension: dimension,
402 metric: format!("{:?}", metric.unwrap_or_default()).to_lowercase(),
403 vector_count: 0,
404 document_count: 0,
405 created_at: "".to_string(),
406 updated_at: "".to_string(),
407 indexing_status: crate::models::IndexingStatus {
408 status: "created".to_string(),
409 progress: 0.0,
410 total_documents: 0,
411 processed_documents: 0,
412 vector_count: 0,
413 estimated_time_remaining: None,
414 last_updated: "".to_string(),
415 },
416 };
417 Ok(info)
418 }
419
420 pub async fn insert_texts(
422 &self,
423 collection: &str,
424 texts: Vec<BatchTextRequest>,
425 ) -> Result<BatchResponse> {
426 let payload = serde_json::json!({
427 "texts": texts
428 });
429
430 let response = self.make_request("POST", &format!("/collections/{}/documents", collection), Some(serde_json::to_value(payload)?)).await?;
431 let batch_response: BatchResponse = serde_json::from_str(&response)
432 .map_err(|e| VectorizerError::server(format!("Failed to parse insert texts response: {}", e)))?;
433 Ok(batch_response)
434 }
435
436 pub async fn delete_collection(&self, name: &str) -> Result<()> {
438 self.make_request("DELETE", &format!("/collections/{}", name), None).await?;
439 Ok(())
440 }
441
442 pub async fn get_vector(&self, collection: &str, vector_id: &str) -> Result<Vector> {
444 let response = self.make_request("GET", &format!("/collections/{}/vectors/{}", collection, vector_id), None).await?;
445 let vector: Vector = serde_json::from_str(&response)
446 .map_err(|e| VectorizerError::server(format!("Failed to parse get vector response: {}", e)))?;
447 Ok(vector)
448 }
449
450 pub async fn get_collection_info(&self, collection: &str) -> Result<CollectionInfo> {
452 let response = self.make_request("GET", &format!("/collections/{}", collection), None).await?;
453 let info: CollectionInfo = serde_json::from_str(&response)
454 .map_err(|e| VectorizerError::server(format!("Failed to parse collection info: {}", e)))?;
455 Ok(info)
456 }
457
458 pub async fn embed_text(&self, text: &str, model: Option<&str>) -> Result<EmbeddingResponse> {
460 let mut payload = serde_json::Map::new();
461 payload.insert("text".to_string(), serde_json::Value::String(text.to_string()));
462
463 if let Some(model) = model {
464 payload.insert("model".to_string(), serde_json::Value::String(model.to_string()));
465 }
466
467 let response = self.make_request("POST", "/embed", Some(serde_json::Value::Object(payload))).await?;
468 let embedding_response: EmbeddingResponse = serde_json::from_str(&response)
469 .map_err(|e| VectorizerError::server(format!("Failed to parse embedding response: {}", e)))?;
470 Ok(embedding_response)
471 }
472
473 pub async fn discover(
479 &self,
480 query: &str,
481 include_collections: Option<Vec<String>>,
482 exclude_collections: Option<Vec<String>>,
483 max_bullets: Option<usize>,
484 broad_k: Option<usize>,
485 focus_k: Option<usize>,
486 ) -> Result<serde_json::Value> {
487 if query.trim().is_empty() {
489 return Err(VectorizerError::validation("Query cannot be empty"));
490 }
491
492 if let Some(max) = max_bullets {
494 if max == 0 {
495 return Err(VectorizerError::validation("max_bullets must be greater than 0"));
496 }
497 }
498
499 let mut payload = serde_json::Map::new();
500 payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
501
502 if let Some(inc) = include_collections {
503 payload.insert("include_collections".to_string(), serde_json::to_value(inc).unwrap());
504 }
505 if let Some(exc) = exclude_collections {
506 payload.insert("exclude_collections".to_string(), serde_json::to_value(exc).unwrap());
507 }
508 if let Some(max) = max_bullets {
509 payload.insert("max_bullets".to_string(), serde_json::Value::Number(max.into()));
510 }
511 if let Some(k) = broad_k {
512 payload.insert("broad_k".to_string(), serde_json::Value::Number(k.into()));
513 }
514 if let Some(k) = focus_k {
515 payload.insert("focus_k".to_string(), serde_json::Value::Number(k.into()));
516 }
517
518 let response = self.make_request("POST", "/discover", Some(serde_json::Value::Object(payload))).await?;
519 let result: serde_json::Value = serde_json::from_str(&response)
520 .map_err(|e| VectorizerError::server(format!("Failed to parse discover response: {}", e)))?;
521 Ok(result)
522 }
523
524 pub async fn filter_collections(
526 &self,
527 query: &str,
528 include: Option<Vec<String>>,
529 exclude: Option<Vec<String>>,
530 ) -> Result<serde_json::Value> {
531 if query.trim().is_empty() {
533 return Err(VectorizerError::validation("Query cannot be empty"));
534 }
535
536 let mut payload = serde_json::Map::new();
537 payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
538
539 if let Some(inc) = include {
540 payload.insert("include".to_string(), serde_json::to_value(inc).unwrap());
541 }
542 if let Some(exc) = exclude {
543 payload.insert("exclude".to_string(), serde_json::to_value(exc).unwrap());
544 }
545
546 let response = self.make_request("POST", "/discovery/filter_collections", Some(serde_json::Value::Object(payload))).await?;
547 let result: serde_json::Value = serde_json::from_str(&response)
548 .map_err(|e| VectorizerError::server(format!("Failed to parse filter response: {}", e)))?;
549 Ok(result)
550 }
551
552 pub async fn score_collections(
554 &self,
555 query: &str,
556 name_match_weight: Option<f32>,
557 term_boost_weight: Option<f32>,
558 signal_boost_weight: Option<f32>,
559 ) -> Result<serde_json::Value> {
560 if let Some(w) = name_match_weight {
562 if w < 0.0 || w > 1.0 {
563 return Err(VectorizerError::validation("name_match_weight must be between 0.0 and 1.0"));
564 }
565 }
566 if let Some(w) = term_boost_weight {
567 if w < 0.0 || w > 1.0 {
568 return Err(VectorizerError::validation("term_boost_weight must be between 0.0 and 1.0"));
569 }
570 }
571 if let Some(w) = signal_boost_weight {
572 if w < 0.0 || w > 1.0 {
573 return Err(VectorizerError::validation("signal_boost_weight must be between 0.0 and 1.0"));
574 }
575 }
576
577 let mut payload = serde_json::Map::new();
578 payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
579
580 if let Some(w) = name_match_weight {
581 payload.insert("name_match_weight".to_string(), serde_json::json!(w));
582 }
583 if let Some(w) = term_boost_weight {
584 payload.insert("term_boost_weight".to_string(), serde_json::json!(w));
585 }
586 if let Some(w) = signal_boost_weight {
587 payload.insert("signal_boost_weight".to_string(), serde_json::json!(w));
588 }
589
590 let response = self.make_request("POST", "/discovery/score_collections", Some(serde_json::Value::Object(payload))).await?;
591 let result: serde_json::Value = serde_json::from_str(&response)
592 .map_err(|e| VectorizerError::server(format!("Failed to parse score response: {}", e)))?;
593 Ok(result)
594 }
595
596 pub async fn expand_queries(
598 &self,
599 query: &str,
600 max_expansions: Option<usize>,
601 include_definition: Option<bool>,
602 include_features: Option<bool>,
603 include_architecture: Option<bool>,
604 ) -> Result<serde_json::Value> {
605 let mut payload = serde_json::Map::new();
606 payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
607
608 if let Some(max) = max_expansions {
609 payload.insert("max_expansions".to_string(), serde_json::Value::Number(max.into()));
610 }
611 if let Some(def) = include_definition {
612 payload.insert("include_definition".to_string(), serde_json::Value::Bool(def));
613 }
614 if let Some(feat) = include_features {
615 payload.insert("include_features".to_string(), serde_json::Value::Bool(feat));
616 }
617 if let Some(arch) = include_architecture {
618 payload.insert("include_architecture".to_string(), serde_json::Value::Bool(arch));
619 }
620
621 let response = self.make_request("POST", "/discovery/expand_queries", Some(serde_json::Value::Object(payload))).await?;
622 let result: serde_json::Value = serde_json::from_str(&response)
623 .map_err(|e| VectorizerError::server(format!("Failed to parse expand response: {}", e)))?;
624 Ok(result)
625 }
626
627 pub async fn get_file_content(
633 &self,
634 collection: &str,
635 file_path: &str,
636 max_size_kb: Option<usize>,
637 ) -> Result<serde_json::Value> {
638 let mut payload = serde_json::Map::new();
639 payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
640 payload.insert("file_path".to_string(), serde_json::Value::String(file_path.to_string()));
641
642 if let Some(max) = max_size_kb {
643 payload.insert("max_size_kb".to_string(), serde_json::Value::Number(max.into()));
644 }
645
646 let response = self.make_request("POST", "/file/content", Some(serde_json::Value::Object(payload))).await?;
647 let result: serde_json::Value = serde_json::from_str(&response)
648 .map_err(|e| VectorizerError::server(format!("Failed to parse file content response: {}", e)))?;
649 Ok(result)
650 }
651
652 pub async fn list_files_in_collection(
654 &self,
655 collection: &str,
656 filter_by_type: Option<Vec<String>>,
657 min_chunks: Option<usize>,
658 max_results: Option<usize>,
659 sort_by: Option<&str>,
660 ) -> Result<serde_json::Value> {
661 let mut payload = serde_json::Map::new();
662 payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
663
664 if let Some(types) = filter_by_type {
665 payload.insert("filter_by_type".to_string(), serde_json::to_value(types).unwrap());
666 }
667 if let Some(min) = min_chunks {
668 payload.insert("min_chunks".to_string(), serde_json::Value::Number(min.into()));
669 }
670 if let Some(max) = max_results {
671 payload.insert("max_results".to_string(), serde_json::Value::Number(max.into()));
672 }
673 if let Some(sort) = sort_by {
674 payload.insert("sort_by".to_string(), serde_json::Value::String(sort.to_string()));
675 }
676
677 let response = self.make_request("POST", "/file/list", Some(serde_json::Value::Object(payload))).await?;
678 let result: serde_json::Value = serde_json::from_str(&response)
679 .map_err(|e| VectorizerError::server(format!("Failed to parse list files response: {}", e)))?;
680 Ok(result)
681 }
682
683 pub async fn get_file_summary(
685 &self,
686 collection: &str,
687 file_path: &str,
688 summary_type: Option<&str>,
689 max_sentences: Option<usize>,
690 ) -> Result<serde_json::Value> {
691 let mut payload = serde_json::Map::new();
692 payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
693 payload.insert("file_path".to_string(), serde_json::Value::String(file_path.to_string()));
694
695 if let Some(stype) = summary_type {
696 payload.insert("summary_type".to_string(), serde_json::Value::String(stype.to_string()));
697 }
698 if let Some(max) = max_sentences {
699 payload.insert("max_sentences".to_string(), serde_json::Value::Number(max.into()));
700 }
701
702 let response = self.make_request("POST", "/file/summary", Some(serde_json::Value::Object(payload))).await?;
703 let result: serde_json::Value = serde_json::from_str(&response)
704 .map_err(|e| VectorizerError::server(format!("Failed to parse file summary response: {}", e)))?;
705 Ok(result)
706 }
707
708 pub async fn get_file_chunks_ordered(
710 &self,
711 collection: &str,
712 file_path: &str,
713 start_chunk: Option<usize>,
714 limit: Option<usize>,
715 include_context: Option<bool>,
716 ) -> Result<serde_json::Value> {
717 let mut payload = serde_json::Map::new();
718 payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
719 payload.insert("file_path".to_string(), serde_json::Value::String(file_path.to_string()));
720
721 if let Some(start) = start_chunk {
722 payload.insert("start_chunk".to_string(), serde_json::Value::Number(start.into()));
723 }
724 if let Some(lim) = limit {
725 payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
726 }
727 if let Some(ctx) = include_context {
728 payload.insert("include_context".to_string(), serde_json::Value::Bool(ctx));
729 }
730
731 let response = self.make_request("POST", "/file/chunks", Some(serde_json::Value::Object(payload))).await?;
732 let result: serde_json::Value = serde_json::from_str(&response)
733 .map_err(|e| VectorizerError::server(format!("Failed to parse chunks response: {}", e)))?;
734 Ok(result)
735 }
736
737 pub async fn get_project_outline(
739 &self,
740 collection: &str,
741 max_depth: Option<usize>,
742 include_summaries: Option<bool>,
743 highlight_key_files: Option<bool>,
744 ) -> Result<serde_json::Value> {
745 let mut payload = serde_json::Map::new();
746 payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
747
748 if let Some(depth) = max_depth {
749 payload.insert("max_depth".to_string(), serde_json::Value::Number(depth.into()));
750 }
751 if let Some(summ) = include_summaries {
752 payload.insert("include_summaries".to_string(), serde_json::Value::Bool(summ));
753 }
754 if let Some(highlight) = highlight_key_files {
755 payload.insert("highlight_key_files".to_string(), serde_json::Value::Bool(highlight));
756 }
757
758 let response = self.make_request("POST", "/file/outline", Some(serde_json::Value::Object(payload))).await?;
759 let result: serde_json::Value = serde_json::from_str(&response)
760 .map_err(|e| VectorizerError::server(format!("Failed to parse outline response: {}", e)))?;
761 Ok(result)
762 }
763
764 pub async fn get_related_files(
766 &self,
767 collection: &str,
768 file_path: &str,
769 limit: Option<usize>,
770 similarity_threshold: Option<f32>,
771 include_reason: Option<bool>,
772 ) -> Result<serde_json::Value> {
773 let mut payload = serde_json::Map::new();
774 payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
775 payload.insert("file_path".to_string(), serde_json::Value::String(file_path.to_string()));
776
777 if let Some(lim) = limit {
778 payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
779 }
780 if let Some(thresh) = similarity_threshold {
781 payload.insert("similarity_threshold".to_string(), serde_json::json!(thresh));
782 }
783 if let Some(reason) = include_reason {
784 payload.insert("include_reason".to_string(), serde_json::Value::Bool(reason));
785 }
786
787 let response = self.make_request("POST", "/file/related", Some(serde_json::Value::Object(payload))).await?;
788 let result: serde_json::Value = serde_json::from_str(&response)
789 .map_err(|e| VectorizerError::server(format!("Failed to parse related files response: {}", e)))?;
790 Ok(result)
791 }
792
793 pub async fn search_by_file_type(
795 &self,
796 collection: &str,
797 query: &str,
798 file_types: Vec<String>,
799 limit: Option<usize>,
800 return_full_files: Option<bool>,
801 ) -> Result<serde_json::Value> {
802 if file_types.is_empty() {
804 return Err(VectorizerError::validation("file_types cannot be empty"));
805 }
806
807 let mut payload = serde_json::Map::new();
808 payload.insert("collection".to_string(), serde_json::Value::String(collection.to_string()));
809 payload.insert("query".to_string(), serde_json::Value::String(query.to_string()));
810 payload.insert("file_types".to_string(), serde_json::to_value(file_types).unwrap());
811
812 if let Some(lim) = limit {
813 payload.insert("limit".to_string(), serde_json::Value::Number(lim.into()));
814 }
815 if let Some(full) = return_full_files {
816 payload.insert("return_full_files".to_string(), serde_json::Value::Bool(full));
817 }
818
819 let response = self.make_request("POST", "/file/search_by_type", Some(serde_json::Value::Object(payload))).await?;
820 let result: serde_json::Value = serde_json::from_str(&response)
821 .map_err(|e| VectorizerError::server(format!("Failed to parse search by type response: {}", e)))?;
822 Ok(result)
823 }
824
825 async fn make_request(
827 &self,
828 method: &str,
829 endpoint: &str,
830 payload: Option<serde_json::Value>,
831 ) -> Result<String> {
832 match method {
833 "GET" => self.transport.get(endpoint).await,
834 "POST" => self.transport.post(endpoint, payload.as_ref()).await,
835 "PUT" => self.transport.put(endpoint, payload.as_ref()).await,
836 "DELETE" => self.transport.delete(endpoint).await,
837 _ => Err(VectorizerError::configuration(format!("Unsupported method: {}", method))),
838 }
839 }
840}