- More keywords.
Some checks are pending
GBCI / build (push) Waiting to run

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-07-29 21:39:24 -03:00
parent ed4aad72f4
commit bcb7703ea6
14 changed files with 384 additions and 38 deletions

189
Cargo.lock generated
View file

@ -268,6 +268,17 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "ahash"
version = "0.8.12"
@ -383,6 +394,15 @@ version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
@ -806,6 +826,15 @@ dependencies = [
"bytes",
]
[[package]]
name = "bzip2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff"
dependencies = [
"libbz2-rs-sys",
]
[[package]]
name = "cc"
version = "1.2.27"
@ -854,6 +883,16 @@ dependencies = [
"windows-link",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "colorchoice"
version = "1.0.4"
@ -904,6 +943,12 @@ dependencies = [
"tiny-keccak",
]
[[package]]
name = "constant_time_eq"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
[[package]]
name = "convert_case"
version = "0.4.0"
@ -1110,6 +1155,12 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "deflate64"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
[[package]]
name = "der"
version = "0.7.10"
@ -1141,6 +1192,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
@ -1475,6 +1537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"libz-rs-sys",
"miniz_oxide",
]
@ -1702,6 +1765,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"urlencoding",
"zip",
]
[[package]]
@ -2333,6 +2397,15 @@ dependencies = [
"hashbrown 0.15.4",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.13"
@ -2532,12 +2605,38 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "libbz2-rs-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775bf80d5878ab7c2b1080b5351a48b2f737d9f6f8b383574eebcc22be0dfccb"
[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "liblzma"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8"
dependencies = [
"liblzma-sys",
]
[[package]]
name = "liblzma-sys"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "libm"
version = "0.2.15"
@ -2555,6 +2654,15 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "libz-rs-sys"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
dependencies = [
"zlib-rs",
]
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
@ -3030,6 +3138,16 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
"hmac",
]
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@ -3230,6 +3348,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppmd-rust"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c834641d8ad1b348c9ee86dec3b9840d805acd5f24daa5f90c788951a52ff59b"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@ -4052,6 +4176,12 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "similar"
version = "2.7.0"
@ -5699,6 +5829,20 @@ name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
]
[[package]]
name = "zerotrie"
@ -5733,6 +5877,51 @@ dependencies = [
"syn 2.0.103",
]
[[package]]
name = "zip"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aed4ac33e8eb078c89e6cbb1d5c4c7703ec6d299fc3e7c3695af8f8b423468b"
dependencies = [
"aes",
"arbitrary",
"bzip2",
"constant_time_eq",
"crc32fast",
"deflate64",
"flate2",
"getrandom 0.3.3",
"hmac",
"indexmap",
"liblzma",
"memchr",
"pbkdf2",
"ppmd-rust",
"sha1",
"time",
"zeroize",
"zopfli",
"zstd",
]
[[package]]
name = "zlib-rs"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
[[package]]
name = "zopfli"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]
[[package]]
name = "zstd"
version = "0.13.3"

View file

@ -42,4 +42,5 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt"] }
scraper = "0.18"
urlencoding = "2.1"
regex = "1.10"
regex = "1.10"
zip = "4.3.0"

View file

@ -13,7 +13,7 @@ use services::script::*;
use services::state::*;
use sqlx::PgPool;
use crate::services::web_automation::BrowserPool;
use crate::services::web_automation::{initialize_browser_pool, BrowserPool};
//use services:: find::*;
mod services;
@ -38,6 +38,10 @@ async fn main() -> std::io::Result<()> {
5,
"/usr/bin/brave-browser-beta".to_string(),
));
initialize_browser_pool()
.await
.expect("Failed to initialize browser pool");
let app_state = web::Data::new(AppState {
db: db.into(),
db_custom: db_custom.into(),

View file

@ -1,14 +1,16 @@
let items = FIND "gb.rob", "ACTION=EMUL"
let items = FIND "gb.rob", "ACTION=EMUL1"
FOR EACH item IN items
PRINT item.company
let website = GET WEBSITE item.company "website"
PRINT website
WAIT 10
let page = GET website
let prompt = "Create a website for " + item.company + " with the following details: " + page
let alias = REWRITE "Return a single word for {item.company} like a token, no spaces, no special characters, no numbers, no uppercase letters."
let alias = LLM "Return a single word for {item.company} like a token, no spaces, no special characters, no numbers, no uppercase letters."
CREATE SITE item.company + "bot", website, "site", prompt
@ -18,4 +20,4 @@ FOR EACH item IN items
CREATE DRAFT to, subject, body
NEXT item
NEXT item

View file

@ -42,7 +42,7 @@ echo "nameserver $PARAM_DNS_INTERNAL_IP" > /etc/resolv.conf
apt install resolvconf -y
apt-get update && apt-get install -y wget libcap2-bin
wget -O /tmp/stalwart.tar.gz https://github.com/stalwartlabs/stalwart/releases/download/v0.12.4/stalwart-x86_64-unknown-linux-gnu.tar.gz
wget -O /tmp/stalwart.tar.gz https://github.com/stalwartlabs/stalwart/releases/download/v0.13.1/stalwart-x86_64-unknown-linux-gnu.tar.gz
tar -xzf /tmp/stalwart.tar.gz -C /tmp
mkdir -p /opt/gbo/bin

View file

@ -0,0 +1,27 @@
export BOT_ID=
./mc alias set minio http://localhost:9000 user pass
./mc admin user add minio $BOT_ID
cat > $BOT_ID-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::pragmatismo-$BOT_ID.gbai",
"arn:aws:s3:::pragmatismo-$BOT_ID.gbai/*"
]
}
]
}
EOF
./mc admin policy create minio $BOT_ID-policy $BOT_ID-policy.json
./mc admin policy attach minio $BOT_ID-policy --user $BOT_ID

View file

@ -4,13 +4,13 @@
declare -A container_limits=(
# Pattern Memory CPU Allowance
["*tables*"]="2048MB:50ms/100ms"
["*dns*"]="512MB:50ms/100ms"
["*dns*"]="2048MB:100ms/100ms"
["*doc-editor*"]="512MB:50ms/100ms"
["*proxy*"]="512MB:50ms/100ms"
["*directory*"]="512MB:50ms/100ms"
["*drive*"]="1024MB:50ms/100ms"
["*email*"]="2048MB:90ms/100ms"
["*webmail*"]="2048MB:90ms/100ms"
["*email*"]="2048MB:50ms/100ms"
["*webmail*"]="2048MB:50ms/100ms"
["*bot*"]="2048MB:50ms/100ms"
["*meeting*"]="1024MB:50ms/100ms"
["*alm*"]="512MB:50ms/100ms"

View file

@ -35,7 +35,7 @@ async fn execute_create_draft(
subject: &str,
reply_text: &str,
) -> Result<String, String> {
let get_result = fetch_latest_email_from_sender(&state.config.clone().unwrap().email, to.clone()).await;
let get_result = fetch_latest_email_from_sender(&state.config.clone().unwrap().email, to).await;
let email_body = if let Ok(get_result_str) = get_result {
if !get_result_str.is_empty() {
get_result_str + reply_text

View file

@ -0,0 +1,27 @@
use rhai::{Dynamic, Engine};
use crate::services::{state::AppState, utils::call_llm};
pub fn llm_keyword(state: &AppState, engine: &mut Engine) {
let ai_config = state.config.clone().unwrap().ai.clone();
engine.register_custom_syntax(
&["LLM", "$string$"], // Syntax: LLM "text to process"
false, // Expression, not statement
move |context, inputs| {
let text = context.eval_expression_tree(&inputs[0])?;
let text_str = text.to_string();
println!("LLM processing text: {}", text_str);
// Use the same pattern as GET
let fut = call_llm(&text_str, &ai_config);
let result = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(fut)
}).map_err(|e| format!("LLM call failed: {}", e))?;
Ok(Dynamic::from(result))
}
).unwrap();
}

View file

@ -4,6 +4,7 @@ pub mod find;
pub mod for_next;
pub mod get;
pub mod get_website;
pub mod llm_keyword;
pub mod print;
pub mod set;
pub mod wait;

View file

@ -8,7 +8,7 @@ use langchain_rust::{
chain::{Chain, LLMChainBuilder},
fmt_message, fmt_template,
language_models::llm::LLM,
llm::{openai::OpenAI, AzureConfig},
llm::{openai::OpenAI},
message_formatter,
prompt::HumanMessagePromptTemplate,
prompt_args,
@ -16,15 +16,7 @@ use langchain_rust::{
template_fstring,
};
use crate::services::{config::AIConfig, state::AppState};
pub fn from_config(config: &AIConfig) -> AzureConfig {
AzureConfig::default()
.with_api_key(&config.key)
.with_api_base(&config.endpoint)
.with_api_version(&config.version)
.with_deployment_id(&config.instance)
}
use crate::services::{ state::AppState, utils::azure_from_config};
#[derive(serde::Deserialize)]
struct ChatRequest {
@ -50,7 +42,7 @@ pub async fn chat(
web::Json(request): web::Json<String>,
state: web::Data<AppState>,
) -> Result<impl Responder, actix_web::Error> {
let azure_config = from_config(&state.config.clone().unwrap().ai);
let azure_config = azure_from_config(&state.config.clone().unwrap().ai);
let open_ai = OpenAI::new(azure_config);
// Parse the context JSON
@ -107,7 +99,7 @@ pub async fn chat_stream(
web::Json(request): web::Json<ChatRequest>,
state: web::Data<AppState>,
) -> Result<impl Responder, actix_web::Error> {
let azure_config = from_config(&state.config.clone().unwrap().ai);
let azure_config = azure_from_config(&state.config.clone().unwrap().ai);
let open_ai = OpenAI::new(azure_config);
let prompt = message_formatter![

View file

@ -5,6 +5,7 @@ use crate::services::keywords::find::{find_keyword};
use crate::services::keywords::for_next::for_keyword;
use crate::services::keywords::get::get_keyword;
use crate::services::keywords::get_website::get_website_keyword;
use crate::services::keywords::llm_keyword::llm_keyword;
use crate::services::keywords::print::print_keyword;
use crate::services::keywords::set::set_keyword;
use crate::services::keywords::wait::wait_keyword;
@ -26,6 +27,7 @@ impl ScriptService {
create_site_keyword(state, &mut engine);
find_keyword(state, &mut engine);
for_keyword(state, &mut engine);
llm_keyword(state, &mut engine);
get_keyword(state, &mut engine);
get_website_keyword(state, &mut engine);
set_keyword(state, &mut engine);

View file

@ -1,3 +1,6 @@
use crate::services::config::AIConfig;
use langchain_rust::llm::OpenAI;
use langchain_rust::{language_models::llm::LLM, llm::AzureConfig};
use log::{debug, warn};
use rhai::{Array, Dynamic};
use serde_json::{json, Value};
@ -5,9 +8,76 @@ use smartstring::SmartString;
use sqlx::Column; // Required for .name() method
use sqlx::TypeInfo; // Required for .type_info() method
use sqlx::{postgres::PgRow, Row};
use std::error::Error;
use sqlx::{Decode, Type};
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use tokio_stream::StreamExt;
use zip::ZipArchive;
use tokio::fs::File as TokioFile;
use reqwest::Client;
use tokio::io::AsyncWriteExt;
pub fn azure_from_config(config: &AIConfig) -> AzureConfig {
AzureConfig::default()
.with_api_key(&config.key)
.with_api_base(&config.endpoint)
.with_api_version(&config.version)
.with_deployment_id(&config.instance)
}
pub async fn call_llm(
text: &str,
ai_config: &AIConfig,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let azure_config = azure_from_config(&ai_config.clone());
let open_ai = OpenAI::new(azure_config);
// Directly use the input text as prompt
let prompt = text.to_string();
// Call LLM and return the raw text response
match open_ai.invoke(&prompt).await {
Ok(response_text) => Ok(response_text),
Err(err) => {
eprintln!("Error invoking LLM API: {}", err);
Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to invoke LLM API",
)))
}
}
}
pub fn extract_zip_recursive(
zip_path: &Path,
destination_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let file = File::open(zip_path)?;
let buf_reader = BufReader::new(file);
let mut archive = ZipArchive::new(buf_reader)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let outpath = destination_path.join(file.mangled_name());
if file.is_dir() {
std::fs::create_dir_all(&outpath)?;
} else {
if let Some(parent) = outpath.parent() {
if !parent.exists() {
std::fs::create_dir_all(&parent)?;
}
}
let mut outfile = File::create(&outpath)?;
std::io::copy(&mut file, &mut outfile)?;
}
}
Ok(())
}
pub fn row_to_json(row: PgRow) -> Result<Value, Box<dyn Error>> {
let mut result = serde_json::Map::new();
let columns = row.columns();
@ -22,7 +92,9 @@ pub fn row_to_json(row: PgRow) -> Result<Value, Box<dyn Error>> {
"INT8" | "int8" => handle_nullable_type::<i64>(&row, i, column_name),
"FLOAT4" | "float4" => handle_nullable_type::<f32>(&row, i, column_name),
"FLOAT8" | "float8" => handle_nullable_type::<f64>(&row, i, column_name),
"TEXT" | "VARCHAR" | "text" | "varchar" => handle_nullable_type::<String>(&row, i, column_name),
"TEXT" | "VARCHAR" | "text" | "varchar" => {
handle_nullable_type::<String>(&row, i, column_name)
}
"BOOL" | "bool" => handle_nullable_type::<bool>(&row, i, column_name),
"JSON" | "JSONB" | "json" | "jsonb" => handle_json(&row, i, column_name),
_ => {
@ -57,7 +129,6 @@ where
}
}
fn handle_json(row: &PgRow, idx: usize, col_name: &str) -> Value {
// First try to get as Option<Value>
match row.try_get::<Option<Value>, _>(idx) {
@ -125,3 +196,23 @@ pub fn to_array(value: Dynamic) -> Array {
Array::from([value])
}
}
pub async fn download_file(url: &str, output_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get(url).send().await?;
if response.status().is_success() {
let mut file = TokioFile::create(output_path).await?;
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
file.write_all(&chunk?).await?;
}
debug!("File downloaded successfully to {}", output_path);
} else {
return Err("Failed to download file".into());
}
Ok(())
}

View file

@ -1,6 +1,9 @@
use crate::services::utils;
use log::debug;
use std::env;
use std::env::temp_dir;
use std::error::Error;
use std::future::Future;
use std::path::Path;
use std::pin::Pin;
use std::process::Command;
use std::sync::Arc;
@ -47,11 +50,10 @@ impl BrowserPool {
caps.add_chrome_arg("--no-sandbox")?;
let driver = WebDriver::new(&self.webdriver_url, caps).await?;
// Execute user function
let result = f(driver).await;
result
}
}
@ -91,11 +93,8 @@ impl BrowserSetup {
}
async fn setup_chromedriver() -> Result<String, Box<dyn std::error::Error>> {
let chromedriver_path = String::from(if cfg!(target_os = "windows") {
"chromedriver.exe"
} else {
"chromedriver"
});
let mut chromedriver_path = env::current_exe()?.parent().unwrap().to_path_buf();
chromedriver_path.push("chromedriver");
// Check if chromedriver exists
if fs::metadata(&chromedriver_path).await.is_err() {
@ -119,14 +118,26 @@ impl BrowserSetup {
_ => return Err("Unsupported platform".into()),
};
let _download_url = format!("{}/chromedriver_{}.zip", base_url, platform);
let zip_path = Path::new("chromedriver.zip");
let download_url = format!("{}/chromedriver_{}.zip", base_url, platform);
let mut zip_path = temp_dir();
zip_path.push("chromedriver.zip");
utils::download_file(&download_url, &zip_path.to_str().unwrap()).await?;
let extract_result = utils::extract_zip_recursive(&zip_path, &chromedriver_path);
if let Err(e) = extract_result {
debug!("Error extracting ZIP: {}", e);
}
// Clean up zip file
let _ = fs::remove_file(&zip_path).await;
if cfg!(target_os = "windows") {
chromedriver_path.push("chromedriver.exe");
} else {
chromedriver_path.push("chromedriver");
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
@ -136,7 +147,7 @@ impl BrowserSetup {
}
}
Ok(chromedriver_path)
Ok(chromedriver_path.to_string_lossy().to_string())
}
}
@ -165,7 +176,6 @@ async fn is_process_running(name: &str) -> bool {
if cfg!(target_os = "windows") {
Command::new("tasklist")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).contains(name))
.unwrap_or(false)
} else {