pub use crate::error::{AiError, CommitError};
pub use crate::prompt::build_payload;
pub use crate::sanitization::sanitize_response;
pub use crate::tui::{open_editor, prompt_action, prompt_custom_instruction, Action};
use crate::openrouter::{self, ApiError};
use std::io::{self, Write};
use std::path::Path;
fn map_api_error(err: ApiError) -> AiError {
match err {
ApiError::HttpError(400) => AiError::ModelUnavailable(err.to_string()),
ApiError::HttpError(404) => AiError::ModelUnavailable(err.to_string()),
ApiError::HttpError(429) => AiError::RateLimitExceeded(err.to_string()),
ApiError::RateLimited => AiError::RateLimitExceeded(err.to_string()),
ApiError::NetworkError(msg) => AiError::Network(msg),
_ => AiError::Api(err.to_string()),
}
}
pub fn run_ai_engine(api_key: &str, model: &str, diff_content: &str) -> Result<String, AiError> {
let payload = build_payload(model, diff_content, "");
let client = openrouter::Client::new(api_key.to_string());
let raw_response = client
.generate_commit_message(&payload)
.map_err(map_api_error)?;
let sanitized = sanitize_response(&raw_response);
if sanitized.is_empty() {
return Err(AiError::EmptyResponse);
}
Ok(sanitized)
}
pub fn regenerate_with_instruction(
api_key: &str,
model: &str,
diff_content: &str,
instruction: &str,
) -> Result<String, AiError> {
let payload = build_payload(model, diff_content, instruction);
print!("⏳ Regenerating...");
io::stdout().flush().ok();
let client = openrouter::Client::new(api_key.to_string());
let raw_response = client
.generate_commit_message(&payload)
.map_err(map_api_error)?;
let sanitized = sanitize_response(&raw_response);
if sanitized.is_empty() {
return Err(AiError::EmptyResponse);
}
Ok(sanitized)
}
pub fn save_draft(draft: &str, repo_root: &Path) -> Result<std::path::PathBuf, CommitError> {
let backup_path = repo_root.join(".git").join("comma_msg.txt");
std::fs::write(&backup_path, draft)?;
Ok(backup_path)
}
pub fn commit_with_draft(draft: &str, repo_root: &Path) -> Result<(), CommitError> {
let backup_path = save_draft(draft, repo_root)?;
let commit_file = backup_path
.to_str()
.ok_or_else(|| CommitError::InvalidPath(backup_path.display().to_string()))?;
let output = std::process::Command::new("git")
.args(["commit", "-F", commit_file])
.current_dir(repo_root)
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(CommitError::HookFailed(stderr.to_string()));
}
if let Err(e) = std::fs::remove_file(&backup_path) {
eprintln!("Warning: Failed to remove backup file: {}", e);
}
Ok(())
}
pub fn get_repo_root() -> Option<std::path::PathBuf> {
let output = std::process::Command::new("git")
.args(["rev-parse", "--show-toplevel"])
.output()
.ok()?;
if output.status.success() {
Some(std::path::PathBuf::from(
String::from_utf8_lossy(&output.stdout).trim(),
))
} else {
None
}
}