Compare commits

..

No commits in common. "e15da792046788ea76b2a01d2d3a1477097ef3ef" and "bcb7703ea69abda823397da2273b5e68ba0e146a" have entirely different histories.

7 changed files with 119 additions and 199 deletions

View file

@ -2,22 +2,22 @@ let items = FIND "gb.rob", "ACTION=EMUL1"
FOR EACH item IN items FOR EACH item IN items
PRINT item.company PRINT item.company
let website = WEBSITE OF item.company let website = GET WEBSITE item.company "website"
PRINT website PRINT website
WAIT 10 WAIT 10
let page = GET website let page = GET website
let prompt = "Create a website for " + item.company + " with the following details: " + page let prompt = "Create a website for " + item.company + " with the following details: " + page
let alias = LLM "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
CREATE SITE item.company + "bot", item.company, website, "site", prompt
let to = item.emailcto let to = item.emailcto
let subject = "Simulador criado " + item.company let subject = "Simulador criado " + item.company
let body = "O simulador " + item.company + " foi criado com sucesso. Acesse o site: " + item.company + "bot" let body = "O simulador " + item.company + " foi criado com sucesso. Acesse o site: " + item.company + "bot"
CREATE_DRAFT to, subject, body CREATE DRAFT to, subject, body
NEXT item NEXT item

View file

@ -9,7 +9,7 @@ pub fn create_draft_keyword(state: &AppState, engine: &mut Engine) {
engine engine
.register_custom_syntax( .register_custom_syntax(
&["CREATE_DRAFT", "$expr$", ",", "$expr$", ",", "$expr$"], &["CREATE", "DRAFT", "$expr$", ",", "$expr$", ",", "$expr$"],
true, // Statement true, // Statement
move |context, inputs| { move |context, inputs| {
// Extract arguments // Extract arguments

View file

@ -1,14 +1,11 @@
use rhai::Dynamic; use rhai::Dynamic;
use rhai::Engine; use rhai::Engine;
use std::error::Error;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use crate::services::state::AppState; use crate::services::state::AppState;
use crate::services::utils;
pub fn create_site_keyword(state: &AppState, engine: &mut Engine) { pub fn create_site_keyword(_state: &AppState, engine: &mut Engine) {
let state_clone = state.clone();
engine engine
.register_custom_syntax( .register_custom_syntax(
&[ &[
@ -22,44 +19,29 @@ pub fn create_site_keyword(state: &AppState, engine: &mut Engine) {
} }
let _name = context.eval_expression_tree(&inputs[0])?; let _name = context.eval_expression_tree(&inputs[0])?;
let company = context.eval_expression_tree(&inputs[1])?;
let _website = context.eval_expression_tree(&inputs[2])?; let _website = context.eval_expression_tree(&inputs[2])?;
let _template = context.eval_expression_tree(&inputs[3])?; let _template = context.eval_expression_tree(&inputs[3])?;
let prompt = context.eval_expression_tree(&inputs[4])?; let prompt = context.eval_expression_tree(&inputs[4])?;
let ai_config = state_clone.config.as_ref().expect("Config must be initialized").ai.clone();
// Use the same pattern as find_keyword
let fut = create_site(&ai_config, _name, prompt);
let result =
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(fut))
.map_err(|e| format!("HTTP request failed: {}", e))?;
Ok(Dynamic::from(result)) // Call the LLM to generate the HTML content
let llm_result = context.call_fn::<String>("chat", (prompt.to_string(),))?;
// Create the directory structure
let base_path = "/opt/gbo/tenants/pragmatismo/proxy/data/websites/sites.pragmatismo.com.br";
let site_name = format!("{}bot", company.to_string());
let full_path = format!("{}/{}", base_path, site_name);
// Create directory if it doesn't exist
fs::create_dir_all(&full_path).map_err(|e| e.to_string())?;
// Write the HTML file
let index_path = Path::new(&full_path).join("index.html");
fs::write(index_path, llm_result).map_err(|e| e.to_string())?;
println!("Site created at: {}", full_path);
Ok(Dynamic::UNIT)
}, },
) )
.unwrap(); .unwrap();
} }
async fn create_site(
ai_config: &crate::services::config::AIConfig,
_name: Dynamic,
prompt: Dynamic,
) -> Result<String, Box<dyn Error + Send + Sync>> {
// Call the LLM to generate the HTML contents
let llm_result = utils::call_llm(&prompt.to_string(), &ai_config).await?;
// Create the directory structure
let base_path = "/opt/gbo/tenants/pragmatismo/proxy/data/websites/sites.pragmatismo.com.br";
let site_name = format!("{}", _name.to_string());
let full_path = format!("{}/{}", base_path, site_name);
// Create directory if it doesn't exist
fs::create_dir_all(&full_path).map_err(|e| e.to_string())?;
// Write the HTML file
let index_path = Path::new(&full_path).join("index.html");
fs::write(index_path, llm_result).map_err(|e| e.to_string())?;
println!("Site created at: {}", full_path);
Ok(full_path)
}

View file

@ -1,4 +1,4 @@
use rhai::{Dynamic, Engine}; use rhai::{Dynamic, Engine};
use reqwest; use reqwest;
use crate::services::state::AppState; use crate::services::state::AppState;
use std::error::Error; use std::error::Error;

View file

@ -11,21 +11,22 @@ pub fn get_website_keyword(state: &AppState, engine: &mut Engine) {
engine engine
.register_custom_syntax( .register_custom_syntax(
&["WEBSITE", "OF", "$expr$"], &["GET", "WEBSITE", "$expr$", "$expr$"],
false, false,
move |context, inputs| { move |context, inputs| {
let search_term = context.eval_expression_tree(&inputs[0])?.to_string(); let search_term = context.eval_expression_tree(&inputs[0])?.to_string();
let website_hint = context.eval_expression_tree(&inputs[1])?.to_string();
println!( println!(
"GET WEBSITE executed - Search: '{}'", "GET WEBSITE executed - Search: '{}', Hint: '{}'",
search_term search_term, website_hint
); );
let browser_pool_clone = browser_pool.clone(); let browser_pool_clone = browser_pool.clone();
let fut = execute_headless_browser_search( let fut = execute_headless_browser_search(
browser_pool_clone, browser_pool_clone,
&search_term &search_term,
&website_hint,
); );
let result = let result =
@ -40,45 +41,60 @@ pub fn get_website_keyword(state: &AppState, engine: &mut Engine) {
pub async fn execute_headless_browser_search( pub async fn execute_headless_browser_search(
browser_pool: Arc<BrowserPool>, // Adjust path as needed browser_pool: Arc<BrowserPool>, // Adjust path as needed
search_term: &str) -> Result<String, Box<dyn Error + Send + Sync>> { search_term: &str,
website_hint: &str,
) -> Result<String, Box<dyn Error + Send + Sync>> {
println!( println!(
"Starting headless browser search: '{}' ", "Starting headless browser search: '{}' targeting '{}'",
search_term search_term, website_hint
); );
let search_term = search_term.to_string(); let search_term = search_term.to_string();
let website_hint = website_hint.to_string();
let result = browser_pool let result = browser_pool
.with_browser(|driver| { .with_browser(|driver| {
Box::pin(async move { perform_search(driver, &search_term).await }) Box::pin(async move { perform_search(driver, &search_term, &website_hint).await })
}) })
.await?; .await?;
Ok(result) Ok(result)
} }
async fn perform_search( async fn perform_search(
driver: WebDriver, driver: WebDriver,
search_term: &str) -> Result<String, Box<dyn Error + Send + Sync>> { search_term: &str,
website_hint: &str,
) -> Result<String, Box<dyn Error + Send + Sync>> {
// Configure the search query
let query = if website_hint.trim().is_empty() {
search_term.to_string()
} else {
format!("{} site:{}", search_term, website_hint)
};
// Navigate to DuckDuckGo // Navigate to DuckDuckGo
println!("Navigating to DuckDuckGo...");
driver.goto("https://duckduckgo.com").await?; driver.goto("https://duckduckgo.com").await?;
// Wait for search box and type query // Wait for search box and type query
let search_input = driver.find(By::Id("searchbox_input")).await?; println!("Searching for: {}", query);
let search_input = driver.find(By::Name("q")).await?;
search_input.click().await?; search_input.click().await?;
search_input.send_keys(search_term).await?; search_input.send_keys(&query).await?;
// Submit search by pressing Enter // Submit search by pressing Enter
search_input.send_keys("\n").await?; search_input.send_keys("\n").await?;
// Wait for results to load - using a modern result selector // Wait for results to load
driver.find(By::Css("[data-testid='result']")).await?; driver.find(By::Css(".result")).await?;
sleep(Duration::from_millis(2000)).await; sleep(Duration::from_millis(2000)).await; // Give extra time for JS
// Extract results // Extract first result link
let results = extract_search_results(&driver).await?; let results = extract_search_results(&driver).await?;
if !results.is_empty() { if !results.is_empty() {
println!("Found {} results", results.len());
Ok(results[0].clone()) Ok(results[0].clone())
} else { } else {
Ok("No results found".to_string()) Ok("No results found".to_string())
@ -90,42 +106,20 @@ async fn extract_search_results(
) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> { ) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
let mut results = Vec::new(); let mut results = Vec::new();
// Try different selectors for search results, ordered by most specific to most general // Try different selectors for search results
let selectors = [ let selectors = [
// Modern DuckDuckGo (as seen in the HTML) "a[data-testid='result-title-a']", // Modern DuckDuckGo
"a[data-testid='result-title-a']", // Primary result links ".result__a", // Classic DuckDuckGo
"a[data-testid='result-extras-url-link']", // URL links in results "a.result-link", // Alternative
"a.eVNpHGjtxRBq_gLOfGDr", // Class-based selector for result titles ".result a[href]", // Generic result links
"a.Rn_JXVtoPVAFyGkcaXyK", // Class-based selector for URL links
".ikg2IXiCD14iVX7AdZo1 a", // Heading container links
".OQ_6vPwNhCeusNiEDcGp a", // URL container links
// Fallback selectors
".result__a", // Classic DuckDuckGo
"a.result-link", // Alternative
".result a[href]", // Generic result links
]; ];
for selector in &selectors { for selector in &selectors {
if let Ok(elements) = driver.find_all(By::Css(selector)).await { if let Ok(elements) = driver.find_all(By::Css(selector)).await {
for element in elements { for element in elements {
if let Ok(Some(href)) = element.attr("href").await { if let Ok(Some(href)) = element.attr("href").await {
// Filter out internal and non-http links if href.starts_with("http") && !href.contains("duckduckgo.com") {
if href.starts_with("http") results.push(href);
&& !href.contains("duckduckgo.com")
&& !href.contains("duck.co")
&& !results.contains(&href) {
// Get the display URL for verification
let display_url = if let Ok(text) = element.text().await {
text.trim().to_string()
} else {
String::new()
};
// Only add if it looks like a real result (not an ad or internal link)
if !display_url.is_empty() && !display_url.contains("Ad") {
results.push(href);
}
} }
} }
} }
@ -135,8 +129,5 @@ async fn extract_search_results(
} }
} }
// Deduplicate results
results.dedup();
Ok(results) Ok(results)
} }

View file

@ -28,8 +28,8 @@ impl ScriptService {
find_keyword(state, &mut engine); find_keyword(state, &mut engine);
for_keyword(state, &mut engine); for_keyword(state, &mut engine);
llm_keyword(state, &mut engine); llm_keyword(state, &mut engine);
get_website_keyword(state, &mut engine);
get_keyword(state, &mut engine); get_keyword(state, &mut engine);
get_website_keyword(state, &mut engine);
set_keyword(state, &mut engine); set_keyword(state, &mut engine);
wait_keyword(state, &mut engine); wait_keyword(state, &mut engine);
print_keyword(state, &mut engine); print_keyword(state, &mut engine);
@ -127,7 +127,6 @@ impl ScriptService {
/// Preprocesses BASIC-style script to handle semicolon-free syntax /// Preprocesses BASIC-style script to handle semicolon-free syntax
pub fn compile(&self, script: &str) -> Result<rhai::AST, Box<EvalAltResult>> { pub fn compile(&self, script: &str) -> Result<rhai::AST, Box<EvalAltResult>> {
let processed_script = self.preprocess_basic_script(script); let processed_script = self.preprocess_basic_script(script);
println!("Processed Script:\n{}", processed_script);
match self.engine.compile(&processed_script) { match self.engine.compile(&processed_script) {
Ok(ast) => Ok(ast), Ok(ast) => Ok(ast),
Err(parse_error) => Err(Box::new(EvalAltResult::from(parse_error))), Err(parse_error) => Err(Box::new(EvalAltResult::from(parse_error))),

View file

@ -1,9 +1,7 @@
// wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
// sudo dpkg -i google-chrome-stable_current_amd64.deb
use crate::services::utils; use crate::services::utils;
use log::debug;
use std::env; use std::env;
use std::env::temp_dir;
use std::error::Error; use std::error::Error;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
@ -47,7 +45,7 @@ impl BrowserPool {
let mut caps = DesiredCapabilities::chrome(); let mut caps = DesiredCapabilities::chrome();
caps.set_binary(&self.brave_path)?; caps.set_binary(&self.brave_path)?;
//caps.add_chrome_arg("--headless=new")?; caps.add_chrome_arg("--headless=new")?;
caps.add_chrome_arg("--disable-gpu")?; caps.add_chrome_arg("--disable-gpu")?;
caps.add_chrome_arg("--no-sandbox")?; caps.add_chrome_arg("--no-sandbox")?;
@ -93,114 +91,64 @@ impl BrowserSetup {
Err("Brave browser not found. Please install Brave first.".into()) Err("Brave browser not found. Please install Brave first.".into())
} }
async fn setup_chromedriver() -> Result<String, Box<dyn std::error::Error>> {
// Create chromedriver directory in executable's parent directory
let mut chromedriver_dir = env::current_exe()?
.parent()
.unwrap()
.to_path_buf();
chromedriver_dir.push("chromedriver");
// Ensure the directory exists
if !chromedriver_dir.exists() {
fs::create_dir(&chromedriver_dir).await?;
}
// Determine the final chromedriver path async fn setup_chromedriver() -> Result<String, Box<dyn std::error::Error>> {
let chromedriver_path = if cfg!(target_os = "windows") { let mut chromedriver_path = env::current_exe()?.parent().unwrap().to_path_buf();
chromedriver_dir.join("chromedriver.exe") chromedriver_path.push("chromedriver");
} else {
chromedriver_dir.join("chromedriver")
};
// Check if chromedriver exists // Check if chromedriver exists
if fs::metadata(&chromedriver_path).await.is_err() { if fs::metadata(&chromedriver_path).await.is_err() {
let (download_url, platform) = match (cfg!(target_os = "windows"), cfg!(target_arch = "x86_64")) { println!("Downloading chromedriver...");
(true, true) => (
"https://storage.googleapis.com/chrome-for-testing-public/138.0.7204.183/win64/chromedriver-win64.zip",
"win64",
),
(true, false) => (
"https://storage.googleapis.com/chrome-for-testing-public/138.0.7204.183/win32/chromedriver-win32.zip",
"win32",
),
(false, true) if cfg!(target_os = "macos") && cfg!(target_arch = "aarch64") => (
"https://storage.googleapis.com/chrome-for-testing-public/138.0.7204.183/mac-arm64/chromedriver-mac-arm64.zip",
"mac-arm64",
),
(false, true) if cfg!(target_os = "macos") => (
"https://storage.googleapis.com/chrome-for-testing-public/138.0.7204.183/mac-x64/chromedriver-mac-x64.zip",
"mac-x64",
),
(false, true) => (
"https://storage.googleapis.com/chrome-for-testing-public/138.0.7204.183/linux64/chromedriver-linux64.zip",
"linux64",
),
_ => return Err("Unsupported platform".into()),
};
let mut zip_path = std::env::temp_dir();
zip_path.push("chromedriver.zip");
println!("Downloading chromedriver for {}...", platform);
// Download the zip file // Note: This URL structure is outdated. Consider using Chrome for Testing endpoints
utils::download_file(download_url, &zip_path.to_str().unwrap()).await?; let (base_url, platform) =
match (cfg!(target_os = "windows"), cfg!(target_arch = "x86_64")) {
(true, true) => (
"https://chromedriver.storage.googleapis.com/114.0.5735.90",
"win32",
),
(false, true) if cfg!(target_os = "macos") => (
"https://chromedriver.storage.googleapis.com/114.0.5735.90",
"mac64",
),
(false, true) => (
"https://chromedriver.storage.googleapis.com/114.0.5735.90",
"linux64",
),
_ => return Err("Unsupported platform".into()),
};
// Extract the zip to a temporary directory first let download_url = format!("{}/chromedriver_{}.zip", base_url, platform);
let mut temp_extract_dir = std::env::temp_dir();
temp_extract_dir.push("chromedriver_extract");
let temp_extract_dir1 = temp_extract_dir.clone();
// Clean up any previous extraction
let _ = fs::remove_dir_all(&temp_extract_dir).await;
fs::create_dir(&temp_extract_dir).await?;
utils::extract_zip_recursive(&zip_path, &temp_extract_dir)?; let mut zip_path = temp_dir();
zip_path.push("chromedriver.zip");
// Chrome for Testing zips contain a platform-specific directory utils::download_file(&download_url, &zip_path.to_str().unwrap()).await?;
// Find the chromedriver binary in the extracted structure
let mut extracted_binary_path = temp_extract_dir; let extract_result = utils::extract_zip_recursive(&zip_path, &chromedriver_path);
extracted_binary_path.push(format!("chromedriver-{}", platform)); if let Err(e) = extract_result {
extracted_binary_path.push(if cfg!(target_os = "windows") { debug!("Error extracting ZIP: {}", e);
"chromedriver.exe" }
// Clean up zip file
let _ = fs::remove_file(&zip_path).await;
if cfg!(target_os = "windows") {
chromedriver_path.push("chromedriver.exe");
} else { } else {
"chromedriver" chromedriver_path.push("chromedriver");
});
// Try to move the file, fall back to copy if cross-device
match fs::rename(&extracted_binary_path, &chromedriver_path).await {
Ok(_) => (),
Err(e) if e.kind() == std::io::ErrorKind::CrossesDevices => {
// Cross-device move failed, use copy instead
fs::copy(&extracted_binary_path, &chromedriver_path).await?;
// Set permissions on the copied file
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&chromedriver_path).await?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&chromedriver_path, perms).await?;
}
},
Err(e) => return Err(e.into()),
} }
// Clean up #[cfg(unix)]
let _ = fs::remove_file(&zip_path).await; {
let _ = fs::remove_dir_all(temp_extract_dir1).await; use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&chromedriver_path).await?.permissions();
// Set executable permissions (if not already set during copy) perms.set_mode(0o755); // Make executable
#[cfg(unix)] fs::set_permissions(&chromedriver_path, perms).await?;
{ }
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&chromedriver_path).await?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&chromedriver_path, perms).await?;
} }
Ok(chromedriver_path.to_string_lossy().to_string())
} }
Ok(chromedriver_path.to_string_lossy().to_string())
}
} }
// Modified BrowserPool initialization // Modified BrowserPool initialization