gpt_commit 0.1.2

A tool to make ChatGPT create a commit message based on a `git diff`.
Documentation
use crate::argument::Args;
use crate::argument::Option;
use crate::choices::get_choices;
use crate::config::Config;
use crate::errors::RunTimeError;
use crate::git::{get_diff, git_commit};
use crate::prompt::Prompt;
use colored::*;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::io::{stdin, stdout, Write};
use tokio::runtime::Runtime;

#[derive(Serialize, Deserialize)]
struct ApiResponse {
    choices: Vec<Choice>,
}

#[derive(Serialize, Deserialize)]
struct Choice {
    message: Message,
}

#[derive(Serialize, Deserialize)]
struct Message {
    role: String,
    content: String,
}

async fn send_api(config: Config, prompt: &String) -> Result<String, Box<dyn std::error::Error>> {
    let message = json!({
        "role": "assistant",
        "content": prompt
    });
    let request_payload = json!({
        "model": config.model(),
        "messages": [message],
    });

    let client = Client::new();
    let response = client
        .post(config.url())
        .header("Content-Type", "application/json")
        .header("Authorization", format!("Bearer {}", config.api_key()))
        .json(&request_payload)
        .send()
        .await?;

    if !response.status().is_success() {
        let error = response.status().to_string();
        return Err(error.into());
    }

    let responce_text = &response.json::<ApiResponse>().await?.choices[0]
        .message
        .content;

    println!("{}", &responce_text.clone().blue());

    Ok(responce_text.to_string())
}

pub fn gpt_commit_run(args: Args, config: Config) -> Result<bool, RunTimeError> {
    let is_using_cached: bool = is_cached(args);

    let change_description: String = get_diff(is_using_cached);
    if change_description.is_empty() {
        return Err(RunTimeError::GitError("Nothing to diff".to_string()));
    }

    let prompt = get_prompt(config.language(), change_description);

    let rt = Runtime::new().unwrap();
    println!("Requesting to ChatGPT...\nPlease wait a moment.");
    let commit_message_result = rt.block_on(async {
        match send_api(config, &prompt).await {
            Ok(api_result) => Ok(choose_commit(api_result)),
            Err(err) => Err(RunTimeError::APIError(err.to_string())),
        }
    });

    let commit_message = match commit_message_result {
        Ok(msg) => msg,
        Err(err) => return Err(err),
    };

    match git_commit(commit_message, is_using_cached) {
        Ok(_) => Ok(true),
        Err(err) => Err(RunTimeError::GitCommitError(err.to_string())),
    }
}
fn choose_commit(api_result_message: String) -> String {
    let choices = get_choices(api_result_message);
    stdout().flush().unwrap();

    let mut input = String::new();
    loop {
        print!("Please choose [1-{}]", choices.len());
        stdout().flush().unwrap();

        stdin().read_line(&mut input).expect("Error reading input");

        if let Ok(choice_number) = input.trim().parse::<usize>() {
            if (1..=choices.len()).contains(&choice_number) {
                return choices[choice_number - 1].clone();
            }
            println!("{}", "Invalid input. Please try again.".yellow());
            input.clear();
        }
    }
}

fn is_cached(args: Args) -> bool {
    *args.option() != Option::NoCache
}

fn get_prompt(language: String, change_description: String) -> String {
    Prompt::new(language, change_description)
        .message()
        .to_string()
}