lc/cli/
vectors.rs

1//! Vector database commands
2
3use crate::cli::VectorCommands;
4use crate::data::vector_db::VectorDatabase;
5use anyhow::Result;
6use colored::*;
7
8/// Handle vector database commands
9pub async fn handle(command: VectorCommands) -> Result<()> {
10    match command {
11        VectorCommands::List => {
12            let databases = VectorDatabase::list_databases()?;
13            
14            if databases.is_empty() {
15                println!("No vector databases found.");
16                println!(
17                    "Create one by running: {}",
18                    "lc embed -d <name> -m <model> \"your text\"".dimmed()
19                );
20            } else {
21                println!("\n{} Vector databases:", "📊".bold().blue());
22                
23                for db_name in databases {
24                    match VectorDatabase::new(&db_name) {
25                        Ok(db) => {
26                            let count = db.count().unwrap_or(0);
27                            let model_info = db.get_model_info().unwrap_or(None);
28                            
29                            print!("  {} {} ({} vectors)", "â€ĸ".blue(), db_name.bold(), count);
30                            
31                            if let Some((model, provider)) = model_info {
32                                print!(" - {}:{}", provider.dimmed(), model.dimmed());
33                            }
34                            
35                            println!();
36                        }
37                        Err(_) => {
38                            println!("  {} {} (error reading)", "â€ĸ".red(), db_name.bold());
39                        }
40                    }
41                }
42            }
43        }
44        VectorCommands::Create { name } => {
45            // Check if database already exists
46            let databases = VectorDatabase::list_databases()?;
47            if databases.contains(&name) {
48                anyhow::bail!("Vector database '{}' already exists", name);
49            }
50            
51            // Create empty database
52            let _ = VectorDatabase::new(&name)?;
53            println!(
54                "{} Vector database '{}' created successfully",
55                "✓".green(),
56                name
57            );
58            println!(
59                "Use {} to add embeddings",
60                format!("lc embed -d {} <text>", name).dimmed()
61            );
62        }
63        VectorCommands::Delete { name, yes } => {
64            // Check if database exists
65            let databases = VectorDatabase::list_databases()?;
66            if !databases.contains(&name) {
67                anyhow::bail!("Vector database '{}' not found", name);
68            }
69            
70            // Ask for confirmation unless --yes is provided
71            if !yes {
72                println!(
73                    "{} Are you sure you want to delete database '{}'? This cannot be undone.",
74                    "âš ī¸".yellow(),
75                    name.bold()
76                );
77                print!("Type 'yes' to confirm: ");
78                use std::io::{self, Write};
79                io::stdout().flush()?;
80                
81                let mut input = String::new();
82                io::stdin().read_line(&mut input)?;
83                
84                if input.trim().to_lowercase() != "yes" {
85                    println!("Deletion cancelled.");
86                    return Ok(());
87                }
88            }
89            
90            VectorDatabase::delete_database(&name)?;
91            println!(
92                "{} Vector database '{}' deleted successfully",
93                "✓".green(),
94                name
95            );
96        }
97        VectorCommands::Info { name } => {
98            // Check if database exists
99            let databases = VectorDatabase::list_databases()?;
100            if !databases.contains(&name) {
101                anyhow::bail!("Vector database '{}' not found", name);
102            }
103            
104            let db = VectorDatabase::new(&name)?;
105            let count = db.count()?;
106            let model_info = db.get_model_info()?;
107            
108            println!("\n{} Database: {}", "â„šī¸".bold().blue(), name.bold());
109            println!("  Vector count: {}", count);
110            
111            if let Some((model, provider)) = model_info {
112                println!("  Model: {}", model);
113                println!("  Provider: {}", provider);
114            } else {
115                println!("  Model: {}", "Not set".dimmed());
116                println!("  Provider: {}", "Not set".dimmed());
117            }
118            
119            // Show recent entries if any
120            if count > 0 {
121                println!("\n{} Recent entries:", "📝".bold().blue());
122                let vectors = db.get_all_vectors()?;
123                for (i, entry) in vectors.iter().take(10).enumerate() {
124                    let preview = if entry.text.len() > 80 {
125                        format!("{}...", &entry.text[..80])
126                    } else {
127                        entry.text.clone()
128                    };
129                    
130                    let source_info = if let Some(ref file_path) = entry.file_path {
131                        if let (Some(chunk_idx), Some(total_chunks)) = 
132                            (entry.chunk_index, entry.total_chunks) 
133                        {
134                            format!(" [{}:{}/{}]", file_path, chunk_idx + 1, total_chunks)
135                        } else {
136                            format!(" [{}]", file_path)
137                        }
138                    } else {
139                        String::new()
140                    };
141                    
142                    println!(
143                        "  {}. {}{} ({})",
144                        i + 1,
145                        preview,
146                        source_info.dimmed(),
147                        entry
148                            .created_at
149                            .format("%Y-%m-%d %H:%M")
150                            .to_string()
151                            .dimmed()
152                    );
153                }
154                
155                if vectors.len() > 10 {
156                    println!("  ... and {} more", vectors.len() - 10);
157                }
158            }
159        }
160        VectorCommands::Stats { name } => {
161            let databases = VectorDatabase::list_databases()?;
162            if !databases.contains(&name) {
163                anyhow::bail!("Vector database '{}' not found", name);
164            }
165            
166            let db = VectorDatabase::new(&name)?;
167            let count = db.count()?;
168            let model_info = db.get_model_info()?;
169            
170            println!("\n{} Vector database: {}", "📊".bold().blue(), name.bold());
171            println!("Vectors: {}", count);
172            
173            if let Some((model, provider)) = model_info {
174                println!("Model: {}:{}", provider, model);
175                
176                // Try to get dimensions from first vector
177                if count > 0 {
178                    let vectors = db.get_all_vectors()?;
179                    if let Some(first) = vectors.first() {
180                        println!("Dimensions: {}", first.vector.len());
181                    }
182                }
183            } else {
184                println!("Model: {}", "Not set".dimmed());
185            }
186            
187            // Get database file size
188            let embeddings_dir = VectorDatabase::embeddings_dir()?;
189            let db_path = embeddings_dir.join(format!("{}.db", name));
190            if let Ok(metadata) = std::fs::metadata(&db_path) {
191                let size_mb = metadata.len() as f64 / (1024.0 * 1024.0);
192                println!("Size: {:.2} MB", size_mb);
193            }
194            
195            if count > 0 {
196                println!("\n{} Recent entries:", "📝".bold().blue());
197                let vectors = db.get_all_vectors()?;
198                for (i, entry) in vectors.iter().take(5).enumerate() {
199                    let preview = if entry.text.len() > 60 {
200                        format!("{}...", &entry.text[..60])
201                    } else {
202                        entry.text.clone()
203                    };
204                    
205                    let source_info = if let Some(ref file_path) = entry.file_path {
206                        if let (Some(chunk_idx), Some(total_chunks)) = 
207                            (entry.chunk_index, entry.total_chunks) 
208                        {
209                            format!(" [{}:{}/{}]", file_path, chunk_idx + 1, total_chunks)
210                        } else {
211                            format!(" [{}]", file_path)
212                        }
213                    } else {
214                        String::new()
215                    };
216                    
217                    println!(
218                        "  {}. {}{} ({})",
219                        i + 1,
220                        preview,
221                        source_info.dimmed(),
222                        entry
223                            .created_at
224                            .format("%Y-%m-%d %H:%M")
225                            .to_string()
226                            .dimmed()
227                    );
228                }
229                
230                if vectors.len() > 5 {
231                    println!("  ... and {} more", vectors.len() - 5);
232                }
233            }
234        }
235        VectorCommands::Clear { name, yes } => {
236            // Check if database exists
237            let databases = VectorDatabase::list_databases()?;
238            if !databases.contains(&name) {
239                anyhow::bail!("Vector database '{}' not found", name);
240            }
241            
242            let db = VectorDatabase::new(&name)?;
243            let count = db.count()?;
244            
245            if count == 0 {
246                println!("Vector database '{}' is already empty.", name);
247                return Ok(());
248            }
249            
250            // Ask for confirmation unless --yes is provided
251            if !yes {
252                println!(
253                    "{} Are you sure you want to clear {} vectors from database '{}'? This cannot be undone.",
254                    "âš ī¸".yellow(),
255                    count,
256                    name.bold()
257                );
258                print!("Type 'yes' to confirm: ");
259                use std::io::{self, Write};
260                io::stdout().flush()?;
261                
262                let mut input = String::new();
263                io::stdin().read_line(&mut input)?;
264                
265                if input.trim().to_lowercase() != "yes" {
266                    println!("Clear operation cancelled.");
267                    return Ok(());
268                }
269            }
270            
271            // Delete and recreate the database to clear it
272            VectorDatabase::delete_database(&name)?;
273            let _ = VectorDatabase::new(&name)?;
274            
275            println!(
276                "{} Vector database '{}' cleared successfully ({} vectors removed)",
277                "✓".green(),
278                name,
279                count
280            );
281        }
282    }
283    
284    Ok(())
285}