2024-11-19 14:59:15 +01:00
|
|
|
use std::collections::HashMap;
|
2024-12-08 16:39:29 +01:00
|
|
|
use sys_locale::get_locale;
|
2024-11-19 14:59:15 +01:00
|
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
2024-11-19 17:02:46 +01:00
|
|
|
use serde_json::Value;
|
2024-11-19 14:59:15 +01:00
|
|
|
|
2024-12-12 16:02:01 +01:00
|
|
|
struct Conf {
|
|
|
|
|
key: String,
|
|
|
|
|
url: String,
|
|
|
|
|
model: String,
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-19 14:59:15 +01:00
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
|
struct Input {
|
|
|
|
|
role: String,
|
|
|
|
|
content: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
|
struct Messages {
|
|
|
|
|
messages: Vec<Input>,
|
|
|
|
|
model: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
|
pub struct AISuggest {
|
2024-12-12 16:17:04 +01:00
|
|
|
pub commands: Vec<String>,
|
2024-11-19 14:59:15 +01:00
|
|
|
pub note: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ai_suggestion(last_command: &str, error_msg: &str) -> Option<AISuggest> {
|
2024-11-20 21:16:14 +01:00
|
|
|
if std::env::var("_PR_AI_DISABLE").is_ok() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 16:02:01 +01:00
|
|
|
let conf = match Conf::new() {
|
|
|
|
|
Some(conf) => conf,
|
|
|
|
|
None => {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-19 21:16:34 +01:00
|
|
|
let error_msg = if error_msg.len() > 300 {
|
|
|
|
|
&error_msg[..300]
|
|
|
|
|
} else {
|
|
|
|
|
error_msg
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-19 14:59:15 +01:00
|
|
|
let mut map = HashMap::new();
|
|
|
|
|
map.insert("last_command", last_command);
|
|
|
|
|
map.insert("error_msg", error_msg);
|
|
|
|
|
|
2024-12-08 16:39:29 +01:00
|
|
|
let user_locale = {
|
|
|
|
|
let locale = std::env::var("_PR_AI_LOCALE")
|
2024-12-12 16:17:04 +01:00
|
|
|
.unwrap_or_else(|_| get_locale().unwrap_or("en-us".to_string()));
|
2024-12-08 16:39:29 +01:00
|
|
|
if locale.len() < 2 {
|
|
|
|
|
"en-US".to_string()
|
|
|
|
|
} else {
|
|
|
|
|
locale
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-20 21:16:14 +01:00
|
|
|
let set_locale = if !user_locale.starts_with("en") {
|
|
|
|
|
format!(". Use language for locale {}", user_locale)
|
2024-11-19 17:16:38 +01:00
|
|
|
} else {
|
|
|
|
|
"".to_string()
|
|
|
|
|
};
|
2024-11-19 14:59:15 +01:00
|
|
|
|
2024-11-19 16:19:22 +01:00
|
|
|
let ai_prompt = format!(
|
|
|
|
|
r#"
|
2024-12-13 12:22:09 +01:00
|
|
|
The command `{last_command}` returns the following error message: `{error_msg}`. Provide possible commands to fix it. Answer in the following exact JSON template without any extra text:
|
2024-11-19 14:59:15 +01:00
|
|
|
```
|
2024-12-12 16:02:01 +01:00
|
|
|
{{"commands":["command 1", "command 2"],"note":"why they may fix the error{set_locale}"}}
|
2024-11-19 14:59:15 +01:00
|
|
|
```
|
2024-11-19 16:19:22 +01:00
|
|
|
"#
|
|
|
|
|
);
|
2024-11-19 14:59:15 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
let res;
|
2024-11-19 14:59:15 +01:00
|
|
|
let messages = Messages {
|
|
|
|
|
messages: vec![Input {
|
|
|
|
|
role: "user".to_string(),
|
|
|
|
|
content: ai_prompt.to_string(),
|
|
|
|
|
}],
|
2024-12-12 16:02:01 +01:00
|
|
|
model: conf.model,
|
2024-11-19 14:59:15 +01:00
|
|
|
};
|
2024-11-19 16:19:22 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
#[cfg(feature = "libcurl")]
|
|
|
|
|
{
|
|
|
|
|
use curl::easy::Easy as Curl;
|
|
|
|
|
use curl::easy::List;
|
|
|
|
|
use std::io::Read;
|
2024-11-22 10:49:24 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
let str_json = serde_json::to_string(&messages).unwrap();
|
|
|
|
|
let mut data = str_json.as_bytes();
|
2024-11-22 10:49:24 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
let mut dst = Vec::new();
|
|
|
|
|
let mut handle = Curl::new();
|
2024-11-22 10:49:24 +01:00
|
|
|
|
2024-12-12 16:02:01 +01:00
|
|
|
handle.url(&conf.url).unwrap();
|
2024-11-22 17:28:49 +01:00
|
|
|
handle.post(true).unwrap();
|
|
|
|
|
handle.post_field_size(data.len() as u64).unwrap();
|
2024-11-22 10:49:24 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
let mut headers = List::new();
|
|
|
|
|
headers
|
2024-12-12 16:02:01 +01:00
|
|
|
.append(&format!("Authorization: Bearer {}", conf.key))
|
2024-11-22 10:49:24 +01:00
|
|
|
.unwrap();
|
2024-11-22 17:28:49 +01:00
|
|
|
headers.append("Content-Type: application/json").unwrap();
|
|
|
|
|
handle.http_headers(headers).unwrap();
|
2024-11-22 10:49:24 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
{
|
|
|
|
|
let mut transfer = handle.transfer();
|
2024-11-22 10:49:24 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
transfer
|
|
|
|
|
.read_function(|buf| Ok(data.read(buf).unwrap_or(0)))
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
transfer
|
|
|
|
|
.write_function(|buf| {
|
|
|
|
|
dst.extend_from_slice(buf);
|
|
|
|
|
Ok(buf.len())
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
2024-11-22 10:49:24 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
transfer.perform().expect("Failed to perform request");
|
|
|
|
|
}
|
2024-11-22 10:49:24 +01:00
|
|
|
|
2024-11-22 17:28:49 +01:00
|
|
|
res = String::from_utf8(dst).unwrap();
|
|
|
|
|
}
|
|
|
|
|
#[cfg(not(feature = "libcurl"))]
|
|
|
|
|
{
|
|
|
|
|
let proc = std::process::Command::new("curl")
|
|
|
|
|
.arg("-X")
|
|
|
|
|
.arg("POST")
|
|
|
|
|
.arg("-H")
|
2024-12-12 16:02:01 +01:00
|
|
|
.arg(format!("Authorization: Bearer {}", conf.key))
|
2024-11-22 17:28:49 +01:00
|
|
|
.arg("-H")
|
|
|
|
|
.arg("Content-Type: application/json")
|
|
|
|
|
.arg("-d")
|
|
|
|
|
.arg(serde_json::to_string(&messages).unwrap())
|
2024-12-12 16:02:01 +01:00
|
|
|
.arg(conf.url)
|
2024-11-22 17:28:49 +01:00
|
|
|
.output();
|
|
|
|
|
|
|
|
|
|
let out = match proc {
|
|
|
|
|
Ok(proc) => proc.stdout,
|
|
|
|
|
Err(_) => {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
res = String::from_utf8(out).unwrap();
|
|
|
|
|
}
|
2024-12-07 15:35:02 +01:00
|
|
|
let json: Value = {
|
|
|
|
|
let json = serde_json::from_str(&res);
|
2024-12-07 17:15:52 +01:00
|
|
|
if let Ok(json) = json {
|
|
|
|
|
json
|
|
|
|
|
} else {
|
2024-12-12 19:18:52 +01:00
|
|
|
eprintln!("AI module: Failed to parse JSON response: {}", res);
|
2024-12-07 15:35:02 +01:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-11-19 22:27:37 +01:00
|
|
|
|
2024-11-19 14:59:15 +01:00
|
|
|
let content = &json["choices"][0]["message"]["content"];
|
|
|
|
|
|
2024-11-19 22:27:37 +01:00
|
|
|
let suggestion: AISuggest = {
|
2024-11-20 09:15:00 +01:00
|
|
|
let str = {
|
|
|
|
|
let str = content.as_str();
|
2024-11-20 10:19:08 +01:00
|
|
|
str?;
|
2024-12-12 19:18:52 +01:00
|
|
|
str.expect("AI module: Failed to get content from response")
|
2024-11-20 10:19:08 +01:00
|
|
|
.trim_start_matches("```")
|
2025-01-07 02:14:27 +01:00
|
|
|
.trim_start_matches("json")
|
2024-11-20 10:19:08 +01:00
|
|
|
.trim_end_matches("```")
|
|
|
|
|
};
|
2024-11-19 22:27:37 +01:00
|
|
|
let json = serde_json::from_str(str);
|
|
|
|
|
if json.is_err() {
|
2024-12-12 19:18:52 +01:00
|
|
|
eprintln!("AI module: Failed to parse JSON content: {}", str);
|
2024-11-19 22:27:37 +01:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
json.unwrap()
|
|
|
|
|
};
|
2024-11-19 14:59:15 +01:00
|
|
|
Some(suggestion)
|
|
|
|
|
}
|
2024-12-12 16:02:01 +01:00
|
|
|
|
|
|
|
|
impl Conf {
|
|
|
|
|
pub fn new() -> Option<Self> {
|
|
|
|
|
let key = match std::env::var("_PR_AI_API_KEY") {
|
|
|
|
|
Ok(key) => key,
|
|
|
|
|
Err(_) => {
|
|
|
|
|
if let Some(key) = option_env!("_DEF_PR_AI_API_KEY") {
|
|
|
|
|
key.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
"Y29uZ3JhdHVsYXRpb25zLCB5b3UgZm91bmQgdGhlIHNlY3JldCE=".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if key.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let url = match std::env::var("_PR_AI_URL") {
|
|
|
|
|
Ok(url) => url,
|
|
|
|
|
Err(_) => {
|
|
|
|
|
if let Some(url) = option_env!("_DEF_PR_AI_URL") {
|
|
|
|
|
url.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
"https://iff.envs.net/completions.py".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if url.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let model = match std::env::var("_PR_AI_MODEL") {
|
|
|
|
|
Ok(model) => model,
|
|
|
|
|
Err(_) => {
|
|
|
|
|
if let Some(model) = option_env!("_DEF_PR_AI_MODEL") {
|
|
|
|
|
model.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
"llama3-8b-8192".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
if model.is_empty() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(Conf { key, url, model })
|
|
|
|
|
}
|
|
|
|
|
}
|