refactor: split request-ai into module

This commit is contained in:
iff 2024-12-08 16:39:29 +01:00
parent fedee1dc8d
commit c9b9f66f89
13 changed files with 151 additions and 106 deletions

12
Cargo.lock generated
View file

@ -471,17 +471,25 @@ name = "pay-respects"
version = "0.5.15" version = "0.5.15"
dependencies = [ dependencies = [
"colored", "colored",
"curl",
"inquire", "inquire",
"pay-respects-parser", "pay-respects-parser",
"pay-respects-utils", "pay-respects-utils",
"regex-lite", "regex-lite",
"rust-i18n", "rust-i18n",
"sys-locale",
]
[[package]]
name = "pay-respects-fallback-request-ai"
version = "0.1.0"
dependencies = [
"colored",
"curl",
"rust-i18n",
"serde", "serde",
"serde_json", "serde_json",
"sys-locale", "sys-locale",
"textwrap", "textwrap",
"toml 0.8.19",
] ]
[[package]] [[package]]

View file

@ -6,7 +6,7 @@ members = [
"pay-respects-utils", "pay-respects-utils",
# optional modules # optional modules
"pay-respects-module-runtime-rules", "pay-respects-module-runtime-rules",
# "pay-respects-module-request-ai", "pay-respects-fallback-request-ai",
] ]
[profile.release] [profile.release]

View file

@ -0,0 +1,21 @@
[package]
name = "pay-respects-fallback-request-ai"
version = "0.1.0"
edition = "2021"
[dependencies]
colored = "2"
sys-locale = "0.3"
rust-i18n = "3"
serde_json = { version = "1.0" }
serde = { version = "1.0", features = ["derive"]}
textwrap = { version = "0.16", features = ["terminal_size"] }
curl = { version = "0.4", optional = true }
[features]
# linking to libcurl dynamically requires openssl when compiling and
# complicates cross compilation
libcurl = ["dep:curl"]

View file

@ -0,0 +1,14 @@
_version = 2
[ai-suggestion]
en = "Suggestion from AI"
es = "Sugerencia de la IA"
de = "Vorschlag von KI"
fr = "Suggestion de l'IA"
it = "Proposta dall'IA"
pt = "Sugestão da IA"
ru = "Предложение от ИИ"
ja = "AIからの提案"
ko = "AI 제안"
zh = "AI 建议"

View file

@ -0,0 +1,36 @@
use crate::requests::ai_suggestion;
use colored::Colorize;
use textwrap::{fill, termwidth};
mod requests;
#[macro_use]
extern crate rust_i18n;
i18n!("i18n", fallback = "en", minify_key = true);
fn main() -> Result<(), std::io::Error> {
let command = std::env::var("_PR_LAST_COMMAND").expect("_PR_LAST_COMMAND not set");
let error = std::env::var("_PR_ERROR_MSG").expect("_PR_ERROR_MSG not set");
colored::control::set_override(true);
#[cfg(debug_assertions)]
{
eprintln!("last_command: {}", command);
eprintln!("error_msg: {}", error);
}
// skip for commands with no arguments,
// very likely to be an error showing the usage
if command.split_whitespace().count() == 1 {
return Ok(());
}
let suggest = ai_suggestion(&command, &error);
if let Some(suggest) = suggest {
let warn = format!("{}:", t!("ai-suggestion")).bold().blue();
let note = fill(&suggest.note, termwidth());
eprintln!("{}\n{}", warn, note);
let command = suggest.command;
print!("{}<_PR_BR>", command);
}
Ok(())
}

View file

@ -1,4 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use sys_locale::get_locale;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@ -71,7 +72,16 @@ pub fn ai_suggestion(last_command: &str, error_msg: &str) -> Option<AISuggest> {
Err(_) => "llama3-8b-8192".to_string(), Err(_) => "llama3-8b-8192".to_string(),
}; };
let user_locale = std::env::var("_PR_AI_LOCALE").unwrap_or("en-US".to_string()); let user_locale = {
let locale = std::env::var("_PR_AI_LOCALE")
.unwrap_or_else(|_| get_locale().unwrap_or("en".to_string()));
if locale.len() < 2 {
"en-US".to_string()
} else {
locale
}
};
let set_locale = if !user_locale.starts_with("en") { let set_locale = if !user_locale.starts_with("en") {
format!(". Use language for locale {}", user_locale) format!(". Use language for locale {}", user_locale)
} else { } else {

View file

@ -19,21 +19,7 @@ sys-locale = "0.3"
rust-i18n = "3" rust-i18n = "3"
regex-lite = "0.1" regex-lite = "0.1"
toml = { version = "0.8", optional = true }
serde_json = { version = "1.0", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
curl = { version = "0.4", optional = true }
textwrap = { version = "0.16", features = ["terminal_size"], optional = true }
inquire = "0.7.5" inquire = "0.7.5"
pay-respects-parser = { version = "0.3.2", path = "../pay-respects-parser" } pay-respects-parser = { version = "0.3.2", path = "../pay-respects-parser" }
pay-respects-utils = {version = "0.1.0", path = "../pay-respects-utils"} pay-respects-utils = {version = "0.1.0", path = "../pay-respects-utils"}
[features]
runtime-rules = ["dep:serde", "dep:toml"]
request-ai = ["dep:serde", "dep:serde_json", "dep:textwrap"]
# linking to libcurl dynamically requires openssl when compiling and
# complicates cross compilation
libcurl = ["dep:curl"]

View file

@ -142,18 +142,6 @@ ja = "不足しているコマンドのパッケージが見つかりました"
ko = "누락된 명령에 대한 패키지가 발견되었습니다" ko = "누락된 명령에 대한 패키지가 발견되었습니다"
zh = "找到缺失命令的包" zh = "找到缺失命令的包"
[ai-suggestion]
en = "Suggestion from AI"
es = "Sugerencia de la IA"
de = "Vorschlag von KI"
fr = "Suggestion de l'IA"
it = "Proposta dall'IA"
pt = "Sugestão da IA"
ru = "Предложение от ИИ"
ja = "AIからの提案"
ko = "AI 제안"
zh = "AI 建议"
[confirm] [confirm]
en = "Execute suggestion?" en = "Execute suggestion?"
es = "¿Ejecutar sugerencia?" es = "¿Ejecutar sugerencia?"

View file

@ -87,17 +87,4 @@ fn print_version() {
"version: {}", "version: {}",
option_env!("CARGO_PKG_VERSION").unwrap_or("unknown") option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")
); );
println!("compile features:");
#[cfg(feature = "runtime-rules")]
{
println!(" - runtime-rules");
}
#[cfg(feature = "request-ai")]
{
println!(" - request-ai");
}
#[cfg(feature = "libcurl")]
{
println!(" - libcurl");
}
} }

View file

@ -24,9 +24,6 @@ mod style;
mod suggestions; mod suggestions;
mod system; mod system;
#[cfg(feature = "request-ai")]
mod requests;
#[macro_use] #[macro_use]
extern crate rust_i18n; extern crate rust_i18n;
i18n!("i18n", fallback = "en", minify_key = true); i18n!("i18n", fallback = "en", minify_key = true);
@ -67,7 +64,7 @@ fn init() -> Result<shell::Data, args::Status> {
let locale = { let locale = {
let sys_locale = get_locale().unwrap_or("en-US".to_string()); let sys_locale = get_locale().unwrap_or("en-US".to_string());
if sys_locale.len() < 2 { if sys_locale.len() < 2 {
"en_US".to_string() "en-US".to_string()
} else { } else {
sys_locale sys_locale
} }
@ -85,12 +82,5 @@ fn init() -> Result<shell::Data, args::Status> {
_ => {} _ => {}
} }
#[cfg(feature = "request-ai")]
{
if std::env::var("_PR_AI_LOCALE").is_err() {
std::env::set_var("_PR_AI_LOCALE", &locale);
}
}
Ok(shell::Data::init()) Ok(shell::Data::init())
} }

View file

@ -2,11 +2,16 @@ use crate::shell::Data;
use pay_respects_parser::parse_rules; use pay_respects_parser::parse_rules;
use pay_respects_utils::evals::*; use pay_respects_utils::evals::*;
pub fn match_pattern(executable: &str, data: &mut Data) { pub fn match_pattern(executable: &str, data: &Data) -> Option<Vec<String>> {
let error_msg = &data.error; let error_msg = &data.error;
let shell = &data.shell; let shell = &data.shell;
let last_command = &data.command; let last_command = &data.command;
let executables = &data.executables; let executables = &data.executables;
let candidates = &mut data.candidates; let mut candidates = vec![];
parse_rules!("rules"); parse_rules!("rules");
if candidates.is_empty() {
None
} else {
Some(candidates)
}
} }

View file

@ -156,21 +156,6 @@ impl Data {
self.suggest = Some(suggest.to_string()); self.suggest = Some(suggest.to_string());
}; };
} }
pub fn add_candidate(&mut self, candidate: &str) {
let candidate = candidate.trim();
if candidate != self.command {
self.candidates.push(candidate.to_string());
}
}
pub fn add_candidates(&mut self, candidates: &Vec<String>) {
for candidate in candidates {
let candidate = candidate.trim();
if candidate != self.command {
self.candidates.push(candidate.to_string());
}
}
}
} }
pub fn elevate(data: &mut Data, command: &mut String) { pub fn elevate(data: &mut Data, command: &mut String) {
@ -182,6 +167,19 @@ pub fn elevate(data: &mut Data, command: &mut String) {
} }
} }
pub fn add_candidates_no_dup(
command: &str,
candidates: &mut Vec<String>,
new_candidates: &Vec<String>,
) {
for candidate in new_candidates {
let candidate = candidate.trim();
if candidate != command && !candidates.contains(&candidate.to_string()) {
candidates.push(candidate.to_string());
}
}
}
pub fn get_error(shell: &str, command: &str) -> String { pub fn get_error(shell: &str, command: &str) -> String {
let error_msg = std::env::var("_PR_ERROR_MSG"); let error_msg = std::env::var("_PR_ERROR_MSG");
let error = if let Ok(error_msg) = error_msg { let error = if let Ok(error_msg) = error_msg {
@ -255,6 +253,10 @@ pub fn module_output(data: &Data, module: &str) -> Option<Vec<String>> {
.output() .output()
.expect("failed to execute process"); .expect("failed to execute process");
if !output.stderr.is_empty() {
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
}
if output.stdout.is_empty() { if output.stdout.is_empty() {
return None; return None;
} }

View file

@ -6,54 +6,52 @@ use colored::Colorize;
use inquire::*; use inquire::*;
use crate::rules::match_pattern; use crate::rules::match_pattern;
use crate::shell::{module_output, shell_evaluated_commands, Data}; use crate::shell::{add_candidates_no_dup, module_output, shell_evaluated_commands, Data};
use crate::style::highlight_difference; use crate::style::highlight_difference;
pub fn suggest_candidates(data: &mut Data) { pub fn suggest_candidates(data: &mut Data) {
let executable = &data.split[0].to_string(); let executable = &data.split[0];
let privilege = &data.privilege.clone(); let command = &data.command;
let privilege = &data.privilege;
let mut suggest_candidates = vec![];
let modules = &data.modules;
let fallbacks = &data.fallbacks;
if privilege.is_none() { if privilege.is_none() {
match_pattern("_PR_privilege", data); if let Some(candidates) = match_pattern("_PR_privilege", data) {
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
}
}
if let Some(candidates) = match_pattern(executable, data) {
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
}
if let Some(candidates) = match_pattern("_PR_general", data) {
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
} }
match_pattern(executable, data);
match_pattern("_PR_general", data);
let modules = &data.modules.clone();
eprintln!("modules: {modules:?}"); eprintln!("modules: {modules:?}");
eprintln!("fallbacks: {fallbacks:?}");
for module in modules { for module in modules {
let candidates = module_output(data, module); let new_candidates = module_output(data, module);
eprintln!("runtime: {candidates:?}");
if candidates.is_some() { if let Some(candidates) = new_candidates {
data.add_candidates(&candidates.unwrap()); add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
} }
} }
#[cfg(feature = "request-ai")] if !suggest_candidates.is_empty() {
{ data.candidates = suggest_candidates;
if !data.candidates.is_empty() {
return; return;
} }
use crate::requests::ai_suggestion; for fallback in fallbacks {
use textwrap::{fill, termwidth}; let candidates = module_output(data, fallback);
let command = &data.command; eprintln!("fallback: {candidates:?}");
let split_command = &data.split; if candidates.is_some() {
let error = &data.error.clone(); add_candidates_no_dup(command, &mut suggest_candidates, &candidates.unwrap());
data.candidates = suggest_candidates;
// skip for commands with no arguments, return;
// very likely to be an error showing the usage
if privilege.is_some() && split_command.len() > 2
|| privilege.is_none() && split_command.len() > 1
{
let suggest = ai_suggestion(command, error);
if let Some(suggest) = suggest {
let warn = format!("{}:", t!("ai-suggestion")).bold().blue();
let note = fill(&suggest.note, termwidth());
eprintln!("{}\n{}\n", warn, note);
let command = suggest.command;
data.add_candidate(&command);
}
} }
} }
} }