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"
dependencies = [
"colored",
"curl",
"inquire",
"pay-respects-parser",
"pay-respects-utils",
"regex-lite",
"rust-i18n",
"sys-locale",
]
[[package]]
name = "pay-respects-fallback-request-ai"
version = "0.1.0"
dependencies = [
"colored",
"curl",
"rust-i18n",
"serde",
"serde_json",
"sys-locale",
"textwrap",
"toml 0.8.19",
]
[[package]]

View file

@ -6,7 +6,7 @@ members = [
"pay-respects-utils",
# optional modules
"pay-respects-module-runtime-rules",
# "pay-respects-module-request-ai",
"pay-respects-fallback-request-ai",
]
[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 sys_locale::get_locale;
use serde::{Deserialize, Serialize};
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(),
};
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") {
format!(". Use language for locale {}", user_locale)
} else {

View file

@ -19,21 +19,7 @@ sys-locale = "0.3"
rust-i18n = "3"
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"
pay-respects-parser = { version = "0.3.2", path = "../pay-respects-parser" }
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 = "누락된 명령에 대한 패키지가 발견되었습니다"
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]
en = "Execute suggestion?"
es = "¿Ejecutar sugerencia?"

View file

@ -87,17 +87,4 @@ fn print_version() {
"version: {}",
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 system;
#[cfg(feature = "request-ai")]
mod requests;
#[macro_use]
extern crate rust_i18n;
i18n!("i18n", fallback = "en", minify_key = true);
@ -67,7 +64,7 @@ fn init() -> Result<shell::Data, args::Status> {
let locale = {
let sys_locale = get_locale().unwrap_or("en-US".to_string());
if sys_locale.len() < 2 {
"en_US".to_string()
"en-US".to_string()
} else {
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())
}

View file

@ -2,11 +2,16 @@ use crate::shell::Data;
use pay_respects_parser::parse_rules;
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 shell = &data.shell;
let last_command = &data.command;
let executables = &data.executables;
let candidates = &mut data.candidates;
let mut candidates = vec![];
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());
};
}
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) {
@ -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 {
let error_msg = std::env::var("_PR_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()
.expect("failed to execute process");
if !output.stderr.is_empty() {
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
}
if output.stdout.is_empty() {
return None;
}

View file

@ -6,55 +6,53 @@ use colored::Colorize;
use inquire::*;
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;
pub fn suggest_candidates(data: &mut Data) {
let executable = &data.split[0].to_string();
let privilege = &data.privilege.clone();
let executable = &data.split[0];
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() {
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!("fallbacks: {fallbacks:?}");
for module in modules {
let candidates = module_output(data, module);
eprintln!("runtime: {candidates:?}");
if candidates.is_some() {
data.add_candidates(&candidates.unwrap());
let new_candidates = module_output(data, module);
if let Some(candidates) = new_candidates {
add_candidates_no_dup(command, &mut suggest_candidates, &candidates);
}
}
#[cfg(feature = "request-ai")]
{
if !data.candidates.is_empty() {
if !suggest_candidates.is_empty() {
data.candidates = suggest_candidates;
return;
}
for fallback in fallbacks {
let candidates = module_output(data, fallback);
eprintln!("fallback: {candidates:?}");
if candidates.is_some() {
add_candidates_no_dup(command, &mut suggest_candidates, &candidates.unwrap());
data.candidates = suggest_candidates;
return;
}
use crate::requests::ai_suggestion;
use textwrap::{fill, termwidth};
let command = &data.command;
let split_command = &data.split;
let error = &data.error.clone();
// skip for commands with no arguments,
// 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);
}
}
}
}