Compare commits
No commits in common. "be17c9b92948a7d74ea39032797deb9844b19d74" and "101a324a2ee9ed4ce8c172a7c5b7bb8cc4d6a350" have entirely different histories.
be17c9b929
...
101a324a2e
12 changed files with 35 additions and 350 deletions
45
.vscode/launch.json
vendored
45
.vscode/launch.json
vendored
|
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'gbserver'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=gbserver",
|
||||
"--package=gbserver"
|
||||
],
|
||||
"filter": {
|
||||
"name": "gbserver",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'gbserver'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=gbserver",
|
||||
"--package=gbserver"
|
||||
],
|
||||
"filter": {
|
||||
"name": "gbserver",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
Cargo.lock
generated
19
Cargo.lock
generated
|
|
@ -409,12 +409,6 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.2"
|
||||
|
|
@ -1756,7 +1750,6 @@ dependencies = [
|
|||
"mailparse",
|
||||
"minio",
|
||||
"native-tls",
|
||||
"num-format",
|
||||
"regex",
|
||||
"reqwest 0.11.27",
|
||||
"rhai",
|
||||
|
|
@ -2606,7 +2599,7 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
||||
dependencies = [
|
||||
"arrayvec 0.5.2",
|
||||
"arrayvec",
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"ryu",
|
||||
|
|
@ -3001,16 +2994,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-format"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.6",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
|
|
|
|||
|
|
@ -60,4 +60,3 @@ urlencoding = "2.1"
|
|||
regex = "1.10"
|
||||
uuid = { version = "1.4", features = ["serde", "v4"] } # v4, v7, etc. as needed
|
||||
zip = "4.3.0"
|
||||
num-format = "0.4"
|
||||
|
|
@ -47,7 +47,7 @@ async fn main() -> std::io::Result<()> {
|
|||
ensure_llama_servers_running()
|
||||
.await
|
||||
.expect("Failed to initialize LLM local server.");
|
||||
|
||||
|
||||
initialize_browser_pool()
|
||||
.await
|
||||
.expect("Failed to initialize browser pool");
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ impl AutomationService {
|
|||
sqlx::query_as::<_, Automation>(
|
||||
r#"
|
||||
SELECT id, kind, target, schedule, param, is_active, last_triggered
|
||||
FROM public.system_automations
|
||||
FROM system_automations
|
||||
WHERE is_active = true
|
||||
"#,
|
||||
)
|
||||
|
|
@ -122,7 +122,7 @@ impl AutomationService {
|
|||
async fn update_last_triggered(&self, automation_id: Uuid) {
|
||||
if let Some(pool) = &self.state.db {
|
||||
if let Err(e) = sqlx::query!(
|
||||
"UPDATE public.system_automations SET last_triggered = $1 WHERE id = $2",
|
||||
"UPDATE system_automations SET last_triggered = $1 WHERE id = $2",
|
||||
Utc::now(),
|
||||
automation_id
|
||||
)
|
||||
|
|
|
|||
|
|
@ -497,7 +497,7 @@ pub async fn save_click(
|
|||
state: web::Data<AppState>,
|
||||
) -> HttpResponse {
|
||||
let (campaign_id, email) = path.into_inner();
|
||||
let _ = sqlx::query("INSERT INTO public.clicks (campaign_id, email, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (campaign_id, email) DO UPDATE SET updated_at = NOW()")
|
||||
let _ = sqlx::query("INSERT INTO clicks (campaign_id, email, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (campaign_id, email) DO UPDATE SET updated_at = NOW()")
|
||||
.bind(campaign_id)
|
||||
.bind(email)
|
||||
.execute(state.db.as_ref().unwrap())
|
||||
|
|
|
|||
|
|
@ -1,193 +0,0 @@
|
|||
use rhai::{Dynamic, Engine};
|
||||
use chrono::{NaiveDateTime, Timelike, Datelike};
|
||||
use num_format::{Locale, ToFormattedString};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub fn format_keyword(engine: &mut Engine) {
|
||||
engine
|
||||
.register_custom_syntax(&["FORMAT", "$expr$", "$expr$"], false, {
|
||||
move |context, inputs| {
|
||||
let value_dyn = context.eval_expression_tree(&inputs[0])?;
|
||||
let pattern_dyn = context.eval_expression_tree(&inputs[1])?;
|
||||
|
||||
let value_str = value_dyn.to_string();
|
||||
let pattern = pattern_dyn.to_string();
|
||||
|
||||
// --- NUMÉRICO ---
|
||||
if let Ok(num) = f64::from_str(&value_str) {
|
||||
let formatted = if pattern.starts_with("N") || pattern.starts_with("C") {
|
||||
// extrai partes: prefixo, casas decimais, locale
|
||||
let (prefix, decimals, locale_tag) = parse_pattern(&pattern);
|
||||
|
||||
let locale = get_locale(&locale_tag);
|
||||
let symbol = if prefix == "C" {
|
||||
get_currency_symbol(&locale_tag)
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let int_part = num.trunc() as i64;
|
||||
let frac_part = num.fract();
|
||||
|
||||
if decimals == 0 {
|
||||
format!("{}{}", symbol, int_part.to_formatted_string(&locale))
|
||||
} else {
|
||||
let frac_scaled =
|
||||
((frac_part * 10f64.powi(decimals as i32)).round()) as i64;
|
||||
format!(
|
||||
"{}{}.{:0width$}",
|
||||
symbol,
|
||||
int_part.to_formatted_string(&locale),
|
||||
frac_scaled,
|
||||
width = decimals
|
||||
)
|
||||
}
|
||||
} else {
|
||||
match pattern.as_str() {
|
||||
"n" => format!("{:.2}", num),
|
||||
"F" => format!("{:.2}", num),
|
||||
"f" => format!("{}", num),
|
||||
"0%" => format!("{:.0}%", num * 100.0),
|
||||
_ => format!("{}", num),
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(Dynamic::from(formatted));
|
||||
}
|
||||
|
||||
// --- DATA ---
|
||||
if let Ok(dt) = NaiveDateTime::parse_from_str(&value_str, "%Y-%m-%d %H:%M:%S") {
|
||||
let formatted = apply_date_format(&dt, &pattern);
|
||||
return Ok(Dynamic::from(formatted));
|
||||
}
|
||||
|
||||
// --- TEXTO ---
|
||||
let formatted = apply_text_placeholders(&value_str, &pattern);
|
||||
Ok(Dynamic::from(formatted))
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// ======================
|
||||
// Extração de locale + precisão
|
||||
// ======================
|
||||
fn parse_pattern(pattern: &str) -> (String, usize, String) {
|
||||
let mut prefix = String::new();
|
||||
let mut decimals: usize = 2; // padrão 2 casas
|
||||
let mut locale_tag = "en".to_string();
|
||||
|
||||
// ex: "C2[pt]" ou "N3[fr]"
|
||||
if pattern.starts_with('C') {
|
||||
prefix = "C".to_string();
|
||||
} else if pattern.starts_with('N') {
|
||||
prefix = "N".to_string();
|
||||
}
|
||||
|
||||
// procura número após prefixo
|
||||
let rest = &pattern[1..];
|
||||
let mut num_part = String::new();
|
||||
for ch in rest.chars() {
|
||||
if ch.is_ascii_digit() {
|
||||
num_part.push(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !num_part.is_empty() {
|
||||
decimals = num_part.parse().unwrap_or(2);
|
||||
}
|
||||
|
||||
// procura locale entre colchetes
|
||||
if let Some(start) = pattern.find('[') {
|
||||
if let Some(end) = pattern.find(']') {
|
||||
if end > start {
|
||||
locale_tag = pattern[start + 1..end].to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(prefix, decimals, locale_tag)
|
||||
}
|
||||
|
||||
fn get_locale(tag: &str) -> Locale {
|
||||
match tag {
|
||||
"en" => Locale::en,
|
||||
"fr" => Locale::fr,
|
||||
"de" => Locale::de,
|
||||
"pt" => Locale::pt,
|
||||
"it" => Locale::it,
|
||||
"es" => Locale::es,
|
||||
_ => Locale::en,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_currency_symbol(tag: &str) -> &'static str {
|
||||
match tag {
|
||||
"en" => "$",
|
||||
"pt" => "R$ ",
|
||||
"fr" | "de" | "es" | "it" => "€",
|
||||
_ => "$",
|
||||
}
|
||||
}
|
||||
|
||||
// ==================
|
||||
// SUPORTE A DATAS
|
||||
// ==================
|
||||
fn apply_date_format(dt: &NaiveDateTime, pattern: &str) -> String {
|
||||
let mut output = pattern.to_string();
|
||||
|
||||
let year = dt.year();
|
||||
let month = dt.month();
|
||||
let day = dt.day();
|
||||
let hour24 = dt.hour();
|
||||
let minute = dt.minute();
|
||||
let second = dt.second();
|
||||
let millis = dt.and_utc().timestamp_subsec_millis();
|
||||
|
||||
output = output.replace("yyyy", &format!("{:04}", year));
|
||||
output = output.replace("yy", &format!("{:02}", year % 100));
|
||||
output = output.replace("MM", &format!("{:02}", month));
|
||||
output = output.replace("M", &format!("{}", month));
|
||||
output = output.replace("dd", &format!("{:02}", day));
|
||||
output = output.replace("d", &format!("{}", day));
|
||||
|
||||
output = output.replace("HH", &format!("{:02}", hour24));
|
||||
output = output.replace("H", &format!("{}", hour24));
|
||||
|
||||
let mut hour12 = hour24 % 12;
|
||||
if hour12 == 0 { hour12 = 12; }
|
||||
output = output.replace("hh", &format!("{:02}", hour12));
|
||||
output = output.replace("h", &format!("{}", hour12));
|
||||
|
||||
output = output.replace("mm", &format!("{:02}", minute));
|
||||
output = output.replace("m", &format!("{}", minute));
|
||||
|
||||
output = output.replace("ss", &format!("{:02}", second));
|
||||
output = output.replace("s", &format!("{}", second));
|
||||
|
||||
output = output.replace("fff", &format!("{:03}", millis));
|
||||
|
||||
output = output.replace("tt", if hour24 < 12 { "AM" } else { "PM" });
|
||||
output = output.replace("t", if hour24 < 12 { "A" } else { "P" });
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
// ==================
|
||||
// SUPORTE A TEXTO
|
||||
// ==================
|
||||
fn apply_text_placeholders(value: &str, pattern: &str) -> String {
|
||||
let mut result = String::new();
|
||||
|
||||
for ch in pattern.chars() {
|
||||
match ch {
|
||||
'@' => result.push_str(value),
|
||||
'&' | '<' => result.push_str(&value.to_lowercase()),
|
||||
'>' | '!' => result.push_str(&value.to_uppercase()),
|
||||
_ => result.push(ch), // copia qualquer caractere literal
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
use rhai::Dynamic;
|
||||
use rhai::Engine;
|
||||
|
||||
pub fn last_keyword(engine: &mut Engine) {
|
||||
engine
|
||||
.register_custom_syntax(&["LAST", "$expr$"], false, {
|
||||
move |context, inputs| {
|
||||
let input_string = context.eval_expression_tree(&inputs[0])?;
|
||||
let input_str = input_string.to_string();
|
||||
|
||||
// Extrai a última palavra dividindo por espaço
|
||||
let last_word = input_str
|
||||
.split_whitespace()
|
||||
.last()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
Ok(Dynamic::from(last_word))
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@ pub mod create_draft;
|
|||
pub mod create_site;
|
||||
pub mod find;
|
||||
pub mod first;
|
||||
pub mod last;
|
||||
pub mod format;
|
||||
pub mod for_next;
|
||||
pub mod get;
|
||||
pub mod get_website;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use dotenv::dotenv;
|
|||
use log::{error, info};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{env, process::Command};
|
||||
use std::env;
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
// OpenAI-compatible request/response structures
|
||||
|
|
@ -188,26 +188,15 @@ async fn start_llm_server(
|
|||
std::env::set_var("OMP_PLACES", "cores");
|
||||
std::env::set_var("OMP_PROC_BIND", "close");
|
||||
|
||||
let mut cmd = Command::new("sh");
|
||||
// "cd {} && numactl --interleave=all ./llama-server -m {} --host 0.0.0.0 --port {} --threads 20 --threads-batch 40 --temp 0.7 --parallel 1 --repeat-penalty 1.1 --ctx-size 8192 --batch-size 8192 -n 4096 --mlock --no-mmap --flash-attn --no-kv-offload --no-mmap &",
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
cmd.arg("-c").arg(format!(
|
||||
"cd {} && ./llama-server -m {} --host 0.0.0.0 --port {} --n-gpu-layers 99 &",
|
||||
llama_cpp_path, model_path, port
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
cmd.arg("/C").arg(format!(
|
||||
"cd {} && llama-server.exe -m {} --host 0.0.0.0 --port {} --n-gpu-layers 99",
|
||||
llama_cpp_path, model_path, port
|
||||
));
|
||||
}
|
||||
let mut cmd = tokio::process::Command::new("sh");
|
||||
cmd.arg("-c").arg(format!(
|
||||
"cd {} && ./llama-server -m {} --host 0.0.0.0 --port {} --n-gpu-layers 99 &",
|
||||
llama_cpp_path, model_path, port
|
||||
));
|
||||
|
||||
cmd.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -217,25 +206,13 @@ async fn start_embedding_server(
|
|||
url: String,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let port = url.split(':').last().unwrap_or("8082");
|
||||
let mut cmd = Command::new("cmd");
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
cmd.arg("/C").arg(format!(
|
||||
"cd {} && llama-server.exe -m {} --host 0.0.0.0 --port {} --embedding --n-gpu-layers 99",
|
||||
llama_cpp_path, model_path, port
|
||||
));
|
||||
|
||||
}
|
||||
let mut cmd = tokio::process::Command::new("sh");
|
||||
cmd.arg("-c").arg(format!(
|
||||
"cd {} && ./llama-server -m {} --host 0.0.0.0 --port {} --embedding --n-gpu-layers 99 &",
|
||||
llama_cpp_path, model_path, port
|
||||
));
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
{
|
||||
cmd.arg("-c").arg(format!(
|
||||
"cd {} && ./llama-server -m {} --host 0.0.0.0 --port {} --embedding --n-gpu-layers 99 &",
|
||||
llama_cpp_path, model_path, port
|
||||
));
|
||||
}
|
||||
|
||||
cmd.spawn()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -454,8 +431,7 @@ struct LlamaCppEmbeddingRequest {
|
|||
// FIXED: Handle the stupid nested array format
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LlamaCppEmbeddingResponseItem {
|
||||
#[serde(rename = "index")]
|
||||
pub _index: usize,
|
||||
pub index: usize,
|
||||
pub embedding: Vec<Vec<f32>>, // This is the up part - embedding is an array of arrays
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ use crate::services::keywords::create_draft::create_draft_keyword;
|
|||
use crate::services::keywords::create_site::create_site_keyword;
|
||||
use crate::services::keywords::find::find_keyword;
|
||||
use crate::services::keywords::first::first_keyword;
|
||||
use crate::services::keywords::last::last_keyword;
|
||||
use crate::services::keywords::format::format_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;
|
||||
|
|
@ -34,8 +32,6 @@ impl ScriptService {
|
|||
find_keyword(state, &mut engine);
|
||||
for_keyword(state, &mut engine);
|
||||
first_keyword(&mut engine);
|
||||
last_keyword(&mut engine);
|
||||
format_keyword(&mut engine);
|
||||
llm_keyword(state, &mut engine);
|
||||
get_website_keyword(state, &mut engine);
|
||||
get_keyword(state, &mut engine);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use std::future::Future;
|
|||
use std::pin::Pin;
|
||||
use std::process::Command;
|
||||
use std::sync::Arc;
|
||||
use std::path::PathBuf;
|
||||
use thirtyfour::{DesiredCapabilities, WebDriver};
|
||||
use tokio::fs;
|
||||
use tokio::sync::Semaphore;
|
||||
|
|
@ -76,32 +75,26 @@ impl BrowserSetup {
|
|||
})
|
||||
}
|
||||
|
||||
async fn find_brave() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut possible_paths = vec![
|
||||
// Windows - Program Files
|
||||
String::from(r"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe"),
|
||||
// macOS
|
||||
String::from("/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"),
|
||||
// Linux
|
||||
String::from("/usr/bin/brave-browser"),
|
||||
String::from("/usr/bin/brave"),
|
||||
];
|
||||
async fn find_brave() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let possible_paths = vec![
|
||||
// Windows
|
||||
String::from(r"C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe"),
|
||||
// macOS
|
||||
String::from("/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"),
|
||||
// Linux
|
||||
String::from("/usr/bin/brave-browser"),
|
||||
String::from("/usr/bin/brave"),
|
||||
];
|
||||
|
||||
// Windows - AppData (usuário atual)
|
||||
if let Ok(local_appdata) = env::var("LOCALAPPDATA") {
|
||||
let mut path = PathBuf::from(local_appdata);
|
||||
path.push("BraveSoftware\\Brave-Browser\\Application\\brave.exe");
|
||||
possible_paths.push(path.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
for path in possible_paths {
|
||||
if fs::metadata(&path).await.is_ok() {
|
||||
return Ok(path);
|
||||
for path in possible_paths {
|
||||
if fs::metadata(&path).await.is_ok() {
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("Brave browser not found. Please install Brave first.".into())
|
||||
} async fn setup_chromedriver() -> Result<String, Box<dyn std::error::Error>> {
|
||||
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");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue