1use anyhow::Result;
12
13use crate::output::{self, print_cid, print_header, print_kv};
14use crate::progress;
15
16fn text_to_embedding(text: &str, dim: usize) -> Vec<f32> {
22 use std::collections::hash_map::DefaultHasher;
23 use std::hash::{Hash, Hasher};
24
25 let mut embedding = vec![0.0f32; dim];
26 for (i, slot) in embedding.iter_mut().enumerate() {
27 let mut hasher = DefaultHasher::new();
28 text.hash(&mut hasher);
29 (i as u64).hash(&mut hasher);
30 let hash_val = hasher.finish();
31 *slot = (hash_val as f32 / u64::MAX as f32) * 2.0 - 1.0;
32 }
33 let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
35 if norm > 0.0 {
36 for x in &mut embedding {
37 *x /= norm;
38 }
39 }
40 embedding
41}
42
43async fn semantic_query_inner(
49 text: &str,
50 top_k: usize,
51 threshold: f32,
52 json_output: bool,
53 print_results: bool,
54) -> Result<Vec<String>> {
55 use ipfrs::{Node, NodeConfig, QueryFilter};
56 use ipfrs_semantic::RouterConfig;
57
58 let mut node = Node::new(NodeConfig::default().with_semantic(RouterConfig::default()))?;
59 node.start().await?;
60
61 let embedding = text_to_embedding(text, 128);
63
64 let filter = QueryFilter {
65 min_score: if threshold > 0.0 {
66 Some(threshold)
67 } else {
68 None
69 },
70 max_score: None,
71 max_results: Some(top_k),
72 cid_prefix: None,
73 };
74
75 let results = match node.search_hybrid(&embedding, top_k, filter).await {
76 Ok(r) => r,
77 Err(_) => {
78 output::warning(
79 "Semantic index not initialized. Use 'ipfrs semantic index <cid>' to index content first.",
80 );
81 node.stop().await?;
82 if json_output && print_results {
83 println!("[]");
84 }
85 return Ok(Vec::new());
86 }
87 };
88
89 node.stop().await?;
90
91 if print_results {
92 if json_output {
93 println!("[");
94 for (idx, result) in results.iter().enumerate() {
95 let comma = if idx + 1 < results.len() { "," } else { "" };
96 println!(
97 " {{\"cid\": \"{}\", \"score\": {:.4}}}{}",
98 result.cid, result.score, comma
99 );
100 }
101 println!("]");
102 } else {
103 print_header(&format!("Semantic search: \"{}\"", text));
104 if threshold > 0.0 {
105 println!(
106 "Found {} results (threshold: {:.2})",
107 results.len(),
108 threshold
109 );
110 } else {
111 println!("Found {} results", results.len());
112 }
113 println!();
114 for result in &results {
115 println!(" CID: {} (score: {:.2})", result.cid, result.score);
116 }
117 }
118 }
119
120 let cids: Vec<String> = results.into_iter().map(|r| r.cid.to_string()).collect();
121 Ok(cids)
122}
123
124pub async fn semantic_query(
128 text: &str,
129 top_k: usize,
130 threshold: f32,
131 json_output: bool,
132) -> Result<()> {
133 semantic_query_inner(text, top_k, threshold, json_output, true).await?;
134 Ok(())
135}
136
137pub async fn semantic_query_with_cids(
142 text: &str,
143 top_k: usize,
144 threshold: f32,
145 json_output: bool,
146) -> Result<Vec<String>> {
147 semantic_query_inner(text, top_k, threshold, json_output, true).await
148}
149
150#[allow(dead_code)]
152pub async fn semantic_search(query: &str, top_k: usize, format: &str) -> Result<()> {
153 let pb = progress::spinner("Searching for similar content...");
154 progress::finish_spinner_success(&pb, "Search initialization complete");
155
156 output::warning("Semantic search requires an embedding model (not yet configured)");
158
159 match format {
160 "json" => {
161 println!("{{");
162 println!(" \"query\": \"{}\",", query);
163 println!(" \"top_k\": {},", top_k);
164 println!(" \"status\": \"not_implemented\",");
165 println!(" \"message\": \"Semantic search requires embedding model configuration\"");
166 println!("}}");
167 }
168 _ => {
169 print_header(&format!("Semantic Search: {}", query));
170 println!("Query: {}", query);
171 println!("Top K: {}", top_k);
172 println!();
173 println!("To enable semantic search:");
174 println!(" 1. Configure an embedding model in config.toml");
175 println!(" 2. Index your content with 'ipfrs semantic index <cid>'");
176 println!(" 3. Run your query again");
177 }
178 }
179
180 Ok(())
181}
182
183#[allow(dead_code)]
185pub async fn semantic_index(cid: &str, metadata: Option<&str>) -> Result<()> {
186 let pb = progress::spinner("Preparing to index content...");
187 progress::finish_spinner_success(&pb, "Index preparation complete");
188
189 output::warning("Semantic indexing requires an embedding model (not yet configured)");
191
192 print_cid("CID", cid);
193 if let Some(meta) = metadata {
194 println!(" Metadata: {}", meta);
195 }
196
197 println!();
198 println!("To enable semantic indexing:");
199 println!(" 1. Configure an embedding model in config.toml");
200 println!(" 2. Ensure the content exists in IPFRS");
201 println!(" 3. Run indexing again to extract and store embeddings");
202
203 Ok(())
204}
205
206#[allow(dead_code)]
208pub async fn semantic_similar(cid: &str, top_k: usize, format: &str) -> Result<()> {
209 let pb = progress::spinner("Preparing similarity search...");
210 progress::finish_spinner_success(&pb, "Search preparation complete");
211
212 output::warning("Similarity search requires an embedding model (not yet configured)");
214
215 match format {
216 "json" => {
217 println!("{{");
218 println!(" \"cid\": \"{}\",", cid);
219 println!(" \"top_k\": {},", top_k);
220 println!(" \"status\": \"not_implemented\",");
221 println!(" \"message\": \"Similarity search requires embedding model configuration\"");
222 println!("}}");
223 }
224 _ => {
225 print_header("Similarity Search");
226 print_cid("Query CID", cid);
227 println!(" Top K: {}", top_k);
228 println!();
229 println!("To enable similarity search:");
230 println!(" 1. Configure an embedding model in config.toml");
231 println!(" 2. Index your content with 'ipfrs semantic index'");
232 println!(" 3. Run similarity search again");
233 }
234 }
235
236 Ok(())
237}
238
239#[allow(dead_code)]
241pub async fn semantic_stats(format: &str) -> Result<()> {
242 let pb = progress::spinner("Retrieving semantic index statistics...");
243 progress::finish_spinner_success(&pb, "Statistics retrieved");
244
245 output::warning("Semantic index not yet initialized");
246
247 match format {
248 "json" => {
249 println!("{{");
250 println!(" \"total_vectors\": 0,");
251 println!(" \"index_size_bytes\": 0,");
252 println!(" \"num_dimensions\": 0,");
253 println!(" \"status\": \"not_initialized\"");
254 println!("}}");
255 }
256 _ => {
257 print_header("Semantic Index Statistics");
258 print_kv("Total Vectors", "0");
259 print_kv("Index Size", "0 B");
260 print_kv("Status", "Not initialized");
261 println!();
262 println!("To initialize the semantic index:");
263 println!(" 1. Configure an embedding model");
264 println!(" 2. Index content with 'ipfrs semantic index <cid>'");
265 }
266 }
267
268 Ok(())
269}
270
271pub async fn semantic_save(path: &str) -> Result<()> {
273 use ipfrs::{Node, NodeConfig};
274
275 let mut node = Node::new(NodeConfig::default())?;
276 node.start().await?;
277
278 println!("Saving semantic index to {}...", path);
279 node.save_semantic_index(path).await?;
280 println!("Semantic index saved successfully");
281
282 node.stop().await?;
283 Ok(())
284}
285
286pub async fn semantic_load(path: &str) -> Result<()> {
288 use ipfrs::{Node, NodeConfig};
289
290 let mut node = Node::new(NodeConfig::default())?;
291 node.start().await?;
292
293 println!("Loading semantic index from {}...", path);
294 node.load_semantic_index(path).await?;
295 println!("Semantic index loaded successfully");
296
297 node.stop().await?;
298 Ok(())
299}