style: Fix rustfmt formatting issues
This commit is contained in:
38
src/cache.rs
38
src/cache.rs
@@ -46,18 +46,16 @@ impl Cache {
|
||||
pub fn load_or_create(cache_path: &Path) -> Self {
|
||||
if cache_path.exists() {
|
||||
match fs::read_to_string(cache_path) {
|
||||
Ok(content) => {
|
||||
match serde_json::from_str::<Cache>(&content) {
|
||||
Ok(cache) => {
|
||||
println!("Loaded cache with {} entries", cache.entries.len());
|
||||
cache
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Cache corrupted, creating new cache");
|
||||
Self::new()
|
||||
}
|
||||
Ok(content) => match serde_json::from_str::<Cache>(&content) {
|
||||
Ok(cache) => {
|
||||
println!("Loaded cache with {} entries", cache.entries.len());
|
||||
cache
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Cache corrupted, creating new cache");
|
||||
Self::new()
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
println!("Failed to read cache, creating new cache");
|
||||
Self::new()
|
||||
@@ -73,13 +71,17 @@ impl Cache {
|
||||
if let Some(parent) = cache_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
|
||||
let content = serde_json::to_string_pretty(self)?;
|
||||
fs::write(cache_path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_cached_response(&self, filenames: &[String], base_path: &Path) -> Option<OrganizationPlan> {
|
||||
pub fn get_cached_response(
|
||||
&self,
|
||||
filenames: &[String],
|
||||
base_path: &Path,
|
||||
) -> Option<OrganizationPlan> {
|
||||
let cache_key = self.generate_cache_key(filenames);
|
||||
|
||||
if let Some(entry) = self.entries.get(&cache_key) {
|
||||
@@ -163,10 +165,7 @@ impl Cache {
|
||||
|
||||
fn get_file_metadata(file_path: &Path) -> Result<FileMetadata, Box<dyn std::error::Error>> {
|
||||
let metadata = fs::metadata(file_path)?;
|
||||
let modified = metadata
|
||||
.modified()?
|
||||
.duration_since(UNIX_EPOCH)?
|
||||
.as_secs();
|
||||
let modified = metadata.modified()?.duration_since(UNIX_EPOCH)?.as_secs();
|
||||
|
||||
Ok(FileMetadata {
|
||||
size: metadata.len(),
|
||||
@@ -182,9 +181,8 @@ impl Cache {
|
||||
|
||||
let initial_count = self.entries.len();
|
||||
|
||||
self.entries.retain(|_, entry| {
|
||||
current_time - entry.timestamp < max_age_seconds
|
||||
});
|
||||
self.entries
|
||||
.retain(|_, entry| current_time - entry.timestamp < max_age_seconds);
|
||||
|
||||
let removed_count = initial_count - self.entries.len();
|
||||
if removed_count > 0 {
|
||||
|
||||
@@ -76,7 +76,8 @@ fn test_cache_response_file_changed() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
let mut file = File::create(&file_path).unwrap();
|
||||
file.write_all(b"modified content longer than original").unwrap();
|
||||
file.write_all(b"modified content longer than original")
|
||||
.unwrap();
|
||||
|
||||
let cached = cache.get_cached_response(&filenames, base_path);
|
||||
assert!(cached.is_none());
|
||||
|
||||
@@ -74,8 +74,9 @@ impl Config {
|
||||
|
||||
pub fn get_or_prompt_api_key() -> Result<String, Box<dyn std::error::Error>> {
|
||||
if let Ok(config) = Config::load()
|
||||
&& !config.api_key.is_empty() {
|
||||
return Ok(config.api_key);
|
||||
&& !config.api_key.is_empty()
|
||||
{
|
||||
return Ok(config.api_key);
|
||||
}
|
||||
|
||||
println!();
|
||||
@@ -94,8 +95,10 @@ pub fn get_or_prompt_api_key() -> Result<String, Box<dyn std::error::Error>> {
|
||||
|
||||
pub fn get_or_prompt_download_folder() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
if let Ok(config) = Config::load()
|
||||
&& !config.download_folder.as_os_str().is_empty() && config.download_folder.exists() {
|
||||
return Ok(config.download_folder);
|
||||
&& !config.download_folder.as_os_str().is_empty()
|
||||
&& config.download_folder.exists()
|
||||
{
|
||||
return Ok(config.download_folder);
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
@@ -18,30 +18,42 @@ fn test_config_serialization() {
|
||||
|
||||
#[test]
|
||||
fn test_validate_api_key_valid() {
|
||||
assert!(crate::prompt::Prompter::validate_api_key("AIzaSyB1234567890123456789012345678"));
|
||||
assert!(crate::prompt::Prompter::validate_api_key("AIzaSyB123456789012345678901234567890"));
|
||||
assert!(crate::prompt::Prompter::validate_api_key(
|
||||
"AIzaSyB1234567890123456789012345678"
|
||||
));
|
||||
assert!(crate::prompt::Prompter::validate_api_key(
|
||||
"AIzaSyB123456789012345678901234567890"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_api_key_invalid() {
|
||||
assert!(!crate::prompt::Prompter::validate_api_key(""));
|
||||
assert!(!crate::prompt::Prompter::validate_api_key("invalid_key"));
|
||||
assert!(!crate::prompt::Prompter::validate_api_key("BizaSyB1234567890123456789012345678"));
|
||||
assert!(!crate::prompt::Prompter::validate_api_key(
|
||||
"BizaSyB1234567890123456789012345678"
|
||||
));
|
||||
assert!(!crate::prompt::Prompter::validate_api_key("short"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_folder_path_valid() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
assert!(crate::prompt::Prompter::validate_folder_path(temp_dir.path()));
|
||||
assert!(crate::prompt::Prompter::validate_folder_path(
|
||||
temp_dir.path()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_folder_path_invalid() {
|
||||
assert!(!crate::prompt::Prompter::validate_folder_path(Path::new("/nonexistent/path/that/does/not/exist")));
|
||||
assert!(!crate::prompt::Prompter::validate_folder_path(Path::new(
|
||||
"/nonexistent/path/that/does/not/exist"
|
||||
)));
|
||||
|
||||
let temp_file = tempfile::NamedTempFile::new().unwrap();
|
||||
assert!(!crate::prompt::Prompter::validate_folder_path(temp_file.path()));
|
||||
assert!(!crate::prompt::Prompter::validate_folder_path(
|
||||
temp_file.path()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
71
src/files.rs
71
src/files.rs
@@ -39,10 +39,11 @@ impl FileBatch {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file()
|
||||
&& let Ok(relative_path) = path.strip_prefix(&root_path) {
|
||||
filenames.push(relative_path.to_string_lossy().into_owned());
|
||||
paths.push(path);
|
||||
}
|
||||
&& let Ok(relative_path) = path.strip_prefix(&root_path)
|
||||
{
|
||||
filenames.push(relative_path.to_string_lossy().into_owned());
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
FileBatch { filenames, paths }
|
||||
@@ -91,10 +92,7 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
eprint!("\nDo you want to apply these changes? [y/N]: ");
|
||||
|
||||
let mut input = String::new();
|
||||
if io::stdin()
|
||||
.read_line(&mut input)
|
||||
.is_err()
|
||||
{
|
||||
if io::stdin().read_line(&mut input).is_err() {
|
||||
eprintln!("\n{}", "Failed to read input. Operation cancelled.".red());
|
||||
return;
|
||||
}
|
||||
@@ -144,11 +142,7 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
match move_file_cross_platform(&source, &target) {
|
||||
Ok(_) => {
|
||||
if item.sub_category.is_empty() {
|
||||
println!(
|
||||
"Moved: {} -> {}/",
|
||||
item.filename,
|
||||
item.category.green()
|
||||
);
|
||||
println!("Moved: {} -> {}/", item.filename, item.category.green());
|
||||
} else {
|
||||
println!(
|
||||
"Moved: {} -> {}/{}",
|
||||
@@ -187,52 +181,19 @@ pub fn execute_move(base_path: &Path, plan: OrganizationPlan) {
|
||||
moved_count.to_string().green(),
|
||||
error_count.to_string().red()
|
||||
);
|
||||
} pub fn is_text_file(path: &Path) -> bool {
|
||||
}
|
||||
pub fn is_text_file(path: &Path) -> bool {
|
||||
let text_extensions = [
|
||||
"txt",
|
||||
"md",
|
||||
"rs",
|
||||
"py",
|
||||
"js",
|
||||
"ts",
|
||||
"jsx",
|
||||
"tsx",
|
||||
"html",
|
||||
"css",
|
||||
"json",
|
||||
"xml",
|
||||
"csv",
|
||||
"yaml",
|
||||
"yml",
|
||||
"toml",
|
||||
"ini",
|
||||
"cfg",
|
||||
"conf",
|
||||
"log",
|
||||
"sh",
|
||||
"bat",
|
||||
"ps1",
|
||||
"sql",
|
||||
"c",
|
||||
"cpp",
|
||||
"h",
|
||||
"hpp",
|
||||
"java",
|
||||
"go",
|
||||
"rb",
|
||||
"php",
|
||||
"swift",
|
||||
"kt",
|
||||
"scala",
|
||||
"lua",
|
||||
"r",
|
||||
"m",
|
||||
"txt", "md", "rs", "py", "js", "ts", "jsx", "tsx", "html", "css", "json", "xml", "csv",
|
||||
"yaml", "yml", "toml", "ini", "cfg", "conf", "log", "sh", "bat", "ps1", "sql", "c", "cpp",
|
||||
"h", "hpp", "java", "go", "rb", "php", "swift", "kt", "scala", "lua", "r", "m",
|
||||
];
|
||||
|
||||
if let Some(ext) = path.extension()
|
||||
&& let Some(ext_str) = ext.to_str() {
|
||||
return text_extensions.contains(&ext_str.to_lowercase().as_str());
|
||||
}
|
||||
&& let Some(ext_str) = ext.to_str()
|
||||
{
|
||||
return text_extensions.contains(&ext_str.to_lowercase().as_str());
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@ fn test_read_file_sample_with_limit() {
|
||||
let file_path = temp_dir.path().join("test.txt");
|
||||
|
||||
let mut file = File::create(&file_path).unwrap();
|
||||
file.write_all(b"Hello, World! This is a long text.").unwrap();
|
||||
file.write_all(b"Hello, World! This is a long text.")
|
||||
.unwrap();
|
||||
|
||||
let content = read_file_sample(&file_path, 5);
|
||||
assert_eq!(content, Some("Hello".to_string()));
|
||||
@@ -92,13 +93,11 @@ fn test_read_file_sample_nonexistent() {
|
||||
#[test]
|
||||
fn test_organization_plan_serialization() {
|
||||
let plan = OrganizationPlan {
|
||||
files: vec![
|
||||
FileCategory {
|
||||
filename: "test.txt".to_string(),
|
||||
category: "Documents".to_string(),
|
||||
sub_category: "Text".to_string(),
|
||||
},
|
||||
],
|
||||
files: vec![FileCategory {
|
||||
filename: "test.txt".to_string(),
|
||||
category: "Documents".to_string(),
|
||||
sub_category: "Text".to_string(),
|
||||
}],
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&plan).unwrap();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::cache::Cache;
|
||||
use crate::files::OrganizationPlan;
|
||||
use crate::gemini_errors::GeminiError;
|
||||
use crate::gemini_helpers::PromptBuilder;
|
||||
use crate::gemini_types::{GeminiResponse, OrganizationPlanResponse};
|
||||
use crate::files::OrganizationPlan;
|
||||
use reqwest::Client;
|
||||
use serde_json::json;
|
||||
use std::path::Path;
|
||||
@@ -71,8 +71,9 @@ impl GeminiClient {
|
||||
let url = self.build_url();
|
||||
|
||||
if let (Some(cache), Some(base_path)) = (cache.as_ref(), base_path)
|
||||
&& let Some(cached_response) = cache.get_cached_response(&filenames, base_path) {
|
||||
return Ok(cached_response);
|
||||
&& let Some(cached_response) = cache.get_cached_response(&filenames, base_path)
|
||||
{
|
||||
return Ok(cached_response);
|
||||
}
|
||||
|
||||
let prompt = PromptBuilder::new(filenames.clone()).build_categorization_prompt();
|
||||
@@ -107,8 +108,8 @@ impl GeminiClient {
|
||||
return Err(GeminiError::from_response(res).await);
|
||||
}
|
||||
|
||||
let gemini_response: GeminiResponse = res.json().await
|
||||
.map_err(GeminiError::NetworkError)?;
|
||||
let gemini_response: GeminiResponse =
|
||||
res.json().await.map_err(GeminiError::NetworkError)?;
|
||||
|
||||
let raw_text = self.extract_text_from_response(&gemini_response)?;
|
||||
let plan_response: OrganizationPlanResponse = serde_json::from_str(&raw_text)?;
|
||||
@@ -116,10 +117,7 @@ impl GeminiClient {
|
||||
Ok(plan_response.to_organization_plan())
|
||||
}
|
||||
|
||||
fn extract_text_from_response(
|
||||
&self,
|
||||
response: &GeminiResponse,
|
||||
) -> Result<String, GeminiError> {
|
||||
fn extract_text_from_response(&self, response: &GeminiResponse) -> Result<String, GeminiError> {
|
||||
response
|
||||
.candidates
|
||||
.first()
|
||||
@@ -244,7 +242,11 @@ impl GeminiClient {
|
||||
self.extract_subcategory_from_response(&gemini_response, filename)
|
||||
}
|
||||
|
||||
fn extract_subcategory_from_response(&self, response: &GeminiResponse, _filename: &str) -> String {
|
||||
fn extract_subcategory_from_response(
|
||||
&self,
|
||||
response: &GeminiResponse,
|
||||
_filename: &str,
|
||||
) -> String {
|
||||
match self.extract_text_from_response(response) {
|
||||
Ok(text) => {
|
||||
let sub_category = text.trim();
|
||||
|
||||
@@ -93,24 +93,27 @@ impl GeminiError {
|
||||
|
||||
fn from_gemini_error(error_detail: GeminiErrorDetail, status: u16) -> Self {
|
||||
let details = error_detail.details;
|
||||
|
||||
|
||||
match error_detail.status.as_str() {
|
||||
"RESOURCE_EXHAUSTED" => {
|
||||
if let Some(retry_info) = details.iter().find(|d| d.retry_delay.is_some())
|
||||
&& let Some(retry_delay) = &retry_info.retry_delay
|
||||
&& let Ok(seconds) = retry_delay.parse::<u32>() {
|
||||
return GeminiError::RateLimitExceeded { retry_after: seconds };
|
||||
}
|
||||
|
||||
if let Some(quota_info) = details.iter().find(|d| d.quota_limit.is_some()) {
|
||||
let limit = quota_info.quota_limit.as_deref().unwrap_or("unknown");
|
||||
return GeminiError::QuotaExceeded {
|
||||
limit: limit.to_string()
|
||||
&& let Ok(seconds) = retry_delay.parse::<u32>()
|
||||
{
|
||||
return GeminiError::RateLimitExceeded {
|
||||
retry_after: seconds,
|
||||
};
|
||||
}
|
||||
|
||||
GeminiError::QuotaExceeded {
|
||||
limit: "usage limit".to_string()
|
||||
|
||||
if let Some(quota_info) = details.iter().find(|d| d.quota_limit.is_some()) {
|
||||
let limit = quota_info.quota_limit.as_deref().unwrap_or("unknown");
|
||||
return GeminiError::QuotaExceeded {
|
||||
limit: limit.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
GeminiError::QuotaExceeded {
|
||||
limit: "usage limit".to_string(),
|
||||
}
|
||||
}
|
||||
"NOT_FOUND" => {
|
||||
@@ -118,69 +121,57 @@ impl GeminiError {
|
||||
let model = extract_model_name(&error_detail.message);
|
||||
GeminiError::ModelNotFound { model }
|
||||
}
|
||||
"UNAUTHENTICATED" => {
|
||||
GeminiError::InvalidApiKey
|
||||
}
|
||||
"UNAUTHENTICATED" => GeminiError::InvalidApiKey,
|
||||
"PERMISSION_DENIED" => {
|
||||
if error_detail.message.to_lowercase().contains("policy") {
|
||||
GeminiError::ContentPolicyViolation {
|
||||
reason: error_detail.message
|
||||
GeminiError::ContentPolicyViolation {
|
||||
reason: error_detail.message,
|
||||
}
|
||||
} else {
|
||||
GeminiError::InvalidRequest {
|
||||
details: error_detail.message
|
||||
GeminiError::InvalidRequest {
|
||||
details: error_detail.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
"INVALID_ARGUMENT" => {
|
||||
GeminiError::InvalidRequest {
|
||||
details: error_detail.message
|
||||
}
|
||||
}
|
||||
"UNAVAILABLE" => {
|
||||
GeminiError::ServiceUnavailable {
|
||||
reason: error_detail.message
|
||||
}
|
||||
}
|
||||
"DEADLINE_EXCEEDED" => {
|
||||
GeminiError::Timeout { seconds: 60 }
|
||||
}
|
||||
"INTERNAL" => {
|
||||
GeminiError::InternalError {
|
||||
details: error_detail.message
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
GeminiError::ApiError {
|
||||
status,
|
||||
message: error_detail.message
|
||||
}
|
||||
}
|
||||
"INVALID_ARGUMENT" => GeminiError::InvalidRequest {
|
||||
details: error_detail.message,
|
||||
},
|
||||
"UNAVAILABLE" => GeminiError::ServiceUnavailable {
|
||||
reason: error_detail.message,
|
||||
},
|
||||
"DEADLINE_EXCEEDED" => GeminiError::Timeout { seconds: 60 },
|
||||
"INTERNAL" => GeminiError::InternalError {
|
||||
details: error_detail.message,
|
||||
},
|
||||
_ => GeminiError::ApiError {
|
||||
status,
|
||||
message: error_detail.message,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn from_status_code(status: reqwest::StatusCode, error_text: &str) -> Self {
|
||||
match status.as_u16() {
|
||||
400 => GeminiError::InvalidRequest {
|
||||
details: error_text.to_string()
|
||||
400 => GeminiError::InvalidRequest {
|
||||
details: error_text.to_string(),
|
||||
},
|
||||
401 => GeminiError::InvalidApiKey,
|
||||
403 => GeminiError::ContentPolicyViolation {
|
||||
reason: error_text.to_string()
|
||||
403 => GeminiError::ContentPolicyViolation {
|
||||
reason: error_text.to_string(),
|
||||
},
|
||||
404 => GeminiError::ModelNotFound {
|
||||
model: "unknown".to_string()
|
||||
404 => GeminiError::ModelNotFound {
|
||||
model: "unknown".to_string(),
|
||||
},
|
||||
429 => GeminiError::RateLimitExceeded { retry_after: 60 },
|
||||
500 => GeminiError::InternalError {
|
||||
details: error_text.to_string()
|
||||
500 => GeminiError::InternalError {
|
||||
details: error_text.to_string(),
|
||||
},
|
||||
502..=504 => GeminiError::ServiceUnavailable {
|
||||
reason: error_text.to_string()
|
||||
502..=504 => GeminiError::ServiceUnavailable {
|
||||
reason: error_text.to_string(),
|
||||
},
|
||||
_ => GeminiError::ApiError {
|
||||
status: status.as_u16(),
|
||||
message: error_text.to_string()
|
||||
_ => GeminiError::ApiError {
|
||||
status: status.as_u16(),
|
||||
message: error_text.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -216,8 +207,9 @@ fn extract_model_name(message: &str) -> String {
|
||||
// Try to extract model name from error message
|
||||
// Example: "Model 'gemini-1.5-flash' not found"
|
||||
if let Some(start) = message.find('\'')
|
||||
&& let Some(end) = message[start + 1..].find('\'') {
|
||||
return message[start + 1..start + 1 + end].to_string();
|
||||
}
|
||||
&& let Some(end) = message[start + 1..].find('\'')
|
||||
{
|
||||
return message[start + 1..start + 1 + end].to_string();
|
||||
}
|
||||
"unknown".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
85
src/main.rs
85
src/main.rs
@@ -60,7 +60,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
};
|
||||
|
||||
println!("{}", "Gemini Plan received! Performing deep inspection...".green());
|
||||
println!(
|
||||
"{}",
|
||||
"Gemini Plan received! Performing deep inspection...".green()
|
||||
);
|
||||
|
||||
let client = Arc::new(client);
|
||||
let semaphore = Arc::new(tokio::sync::Semaphore::new(args.max_concurrent));
|
||||
@@ -81,7 +84,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let _permit = semaphore.acquire().await.unwrap();
|
||||
if let Some(content) = noentropy::files::read_file_sample(&path, 5000) {
|
||||
println!("Reading content of {}...", filename.green());
|
||||
client.get_ai_sub_category(&filename, &category, &content).await
|
||||
client
|
||||
.get_ai_sub_category(&filename, &category, &content)
|
||||
.await
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
@@ -101,10 +106,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("{}", "Deep inspection complete! Moving Files.....".green());
|
||||
|
||||
if args.dry_run {
|
||||
println!(
|
||||
"{} Dry run mode - skipping file moves.",
|
||||
"INFO:".cyan()
|
||||
);
|
||||
println!("{} Dry run mode - skipping file moves.", "INFO:".cyan());
|
||||
} else {
|
||||
execute_move(&download_path, plan);
|
||||
}
|
||||
@@ -119,62 +121,77 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
fn handle_gemini_error(error: GeminiError) {
|
||||
use colored::*;
|
||||
|
||||
|
||||
match error {
|
||||
GeminiError::RateLimitExceeded { retry_after } => {
|
||||
println!("{} API rate limit exceeded. Please wait {} seconds before trying again.",
|
||||
"ERROR:".red(), retry_after);
|
||||
println!(
|
||||
"{} API rate limit exceeded. Please wait {} seconds before trying again.",
|
||||
"ERROR:".red(),
|
||||
retry_after
|
||||
);
|
||||
}
|
||||
GeminiError::QuotaExceeded { limit } => {
|
||||
println!("{} Quota exceeded: {}. Please check your Gemini API usage.",
|
||||
"ERROR:".red(), limit);
|
||||
println!(
|
||||
"{} Quota exceeded: {}. Please check your Gemini API usage.",
|
||||
"ERROR:".red(),
|
||||
limit
|
||||
);
|
||||
}
|
||||
GeminiError::ModelNotFound { model } => {
|
||||
println!("{} Model '{}' not found. Please check the model name in the configuration.",
|
||||
"ERROR:".red(), model);
|
||||
println!(
|
||||
"{} Model '{}' not found. Please check the model name in the configuration.",
|
||||
"ERROR:".red(),
|
||||
model
|
||||
);
|
||||
}
|
||||
GeminiError::InvalidApiKey => {
|
||||
println!("{} Invalid API key. Please check your GEMINI_API_KEY environment variable.",
|
||||
"ERROR:".red());
|
||||
println!(
|
||||
"{} Invalid API key. Please check your GEMINI_API_KEY environment variable.",
|
||||
"ERROR:".red()
|
||||
);
|
||||
}
|
||||
GeminiError::ContentPolicyViolation { reason } => {
|
||||
println!("{} Content policy violation: {}",
|
||||
"ERROR:".red(), reason);
|
||||
println!("{} Content policy violation: {}", "ERROR:".red(), reason);
|
||||
}
|
||||
GeminiError::ServiceUnavailable { reason } => {
|
||||
println!("{} Gemini service is temporarily unavailable: {}",
|
||||
"ERROR:".red(), reason);
|
||||
println!(
|
||||
"{} Gemini service is temporarily unavailable: {}",
|
||||
"ERROR:".red(),
|
||||
reason
|
||||
);
|
||||
}
|
||||
GeminiError::NetworkError(e) => {
|
||||
println!("{} Network error: {}",
|
||||
"ERROR:".red(), e);
|
||||
println!("{} Network error: {}", "ERROR:".red(), e);
|
||||
}
|
||||
GeminiError::Timeout { seconds } => {
|
||||
println!("{} Request timed out after {} seconds.",
|
||||
"ERROR:".red(), seconds);
|
||||
println!(
|
||||
"{} Request timed out after {} seconds.",
|
||||
"ERROR:".red(),
|
||||
seconds
|
||||
);
|
||||
}
|
||||
GeminiError::InvalidRequest { details } => {
|
||||
println!("{} Invalid request: {}",
|
||||
"ERROR:".red(), details);
|
||||
println!("{} Invalid request: {}", "ERROR:".red(), details);
|
||||
}
|
||||
GeminiError::ApiError { status, message } => {
|
||||
println!("{} API error (HTTP {}): {}",
|
||||
"ERROR:".red(), status, message);
|
||||
println!(
|
||||
"{} API error (HTTP {}): {}",
|
||||
"ERROR:".red(),
|
||||
status,
|
||||
message
|
||||
);
|
||||
}
|
||||
GeminiError::InvalidResponse(msg) => {
|
||||
println!("{} Invalid response from Gemini: {}",
|
||||
"ERROR:".red(), msg);
|
||||
println!("{} Invalid response from Gemini: {}", "ERROR:".red(), msg);
|
||||
}
|
||||
GeminiError::InternalError { details } => {
|
||||
println!("{} Internal server error: {}",
|
||||
"ERROR:".red(), details);
|
||||
println!("{} Internal server error: {}", "ERROR:".red(), details);
|
||||
}
|
||||
GeminiError::SerializationError(e) => {
|
||||
println!("{} JSON serialization error: {}",
|
||||
"ERROR:".red(), e);
|
||||
println!("{} JSON serialization error: {}", "ERROR:".red(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
println!("\n{} Check the following:", "HINT:".yellow());
|
||||
println!(" • Your GEMINI_API_KEY is correctly set");
|
||||
println!(" • Your internet connection is working");
|
||||
|
||||
@@ -10,7 +10,10 @@ pub struct Prompter;
|
||||
impl Prompter {
|
||||
pub fn prompt_api_key() -> Result<String, Box<dyn std::error::Error>> {
|
||||
println!();
|
||||
println!("Get your API key at: {}", "https://ai.google.dev/".cyan().underline());
|
||||
println!(
|
||||
"Get your API key at: {}",
|
||||
"https://ai.google.dev/".cyan().underline()
|
||||
);
|
||||
println!("Enter your API Key (starts with 'AIza'):");
|
||||
|
||||
let mut attempts = 0;
|
||||
@@ -29,7 +32,10 @@ impl Prompter {
|
||||
}
|
||||
|
||||
attempts += 1;
|
||||
Self::print_validation_error("Invalid API key format. Must start with 'AIza' and be around 39 characters.", attempts);
|
||||
Self::print_validation_error(
|
||||
"Invalid API key format. Must start with 'AIza' and be around 39 characters.",
|
||||
attempts,
|
||||
);
|
||||
}
|
||||
|
||||
Err("Max retries exceeded. Please run again with a valid API key.".into())
|
||||
@@ -102,10 +108,7 @@ impl Prompter {
|
||||
}
|
||||
|
||||
pub fn validate_api_key(key: &str) -> bool {
|
||||
!key.is_empty()
|
||||
&& key.starts_with("AIza")
|
||||
&& key.len() >= 35
|
||||
&& key.len() <= 50
|
||||
!key.is_empty() && key.starts_with("AIza") && key.len() >= 35 && key.len() <= 50
|
||||
}
|
||||
|
||||
pub fn validate_folder_path(path: &Path) -> bool {
|
||||
@@ -120,9 +123,10 @@ impl Prompter {
|
||||
|
||||
pub fn expand_home(path: &str) -> String {
|
||||
if path.starts_with("~/")
|
||||
&& let Some(base_dirs) = BaseDirs::new() {
|
||||
let home = base_dirs.home_dir();
|
||||
return path.replacen("~", &home.to_string_lossy(), 1);
|
||||
&& let Some(base_dirs) = BaseDirs::new()
|
||||
{
|
||||
let home = base_dirs.home_dir();
|
||||
return path.replacen("~", &home.to_string_lossy(), 1);
|
||||
}
|
||||
path.to_string()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user