doom and eyes fixes
This commit is contained in:
@@ -36,7 +36,6 @@ impl ToxicityClassifier {
|
||||
|
||||
let seq_len = ids.len();
|
||||
|
||||
// В tract .into_shape() возвращает Tensor напрямую, unwrap не нужен
|
||||
let ids_tensor = tensor1(&ids)
|
||||
.into_shape(&[1, seq_len])
|
||||
.expect("Shape error");
|
||||
@@ -56,20 +55,37 @@ impl ToxicityClassifier {
|
||||
let outputs = match result {
|
||||
Ok(out) => out,
|
||||
Err(e) => {
|
||||
println!("AI Link Error: {:?}", e);
|
||||
println!("💥 [MODEL ERR]: {:?}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Извлекаем результат
|
||||
let logits = match outputs[0].to_array_view::<f32>() {
|
||||
Ok(view) => view,
|
||||
// --- БРОНЕБОЙНОЕ РЕШЕНИЕ (Никаких паник ndarray) ---
|
||||
let view = match outputs[0].to_array_view::<f32>() {
|
||||
Ok(v) => v,
|
||||
Err(_) => return false,
|
||||
};
|
||||
|
||||
logits.iter().any(|&x| {
|
||||
let prob = 1.0 / (1.0 + (-x).exp());
|
||||
prob > 0.8
|
||||
})
|
||||
// Превращаем любую матрицу (хоть 2D, хоть 3D) в плоский список чисел
|
||||
let logits: Vec<f32> = view.iter().copied().collect();
|
||||
|
||||
// Теперь logits это просто одномерный массив, например [-2.5, 4.8]
|
||||
let toxic_logit = if logits.len() >= 2 {
|
||||
logits[1] // Вероятность мата
|
||||
} else if logits.len() == 1 {
|
||||
logits[0]
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Применяем сигмоиду
|
||||
let prob = 1.0 / (1.0 + (-toxic_logit).exp());
|
||||
|
||||
println!(
|
||||
"🤖 [AI DEBUG] Text: '{}' | Logits: {:?} | Prob: {:.4}",
|
||||
text, logits, prob
|
||||
);
|
||||
|
||||
prob > 0.85 // Порог 85%
|
||||
}
|
||||
}
|
||||
|
||||
+67
-140
@@ -6,9 +6,7 @@ use axum::{
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use regex::Regex;
|
||||
use reqwest::Client;
|
||||
use rustrict::CensorStr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
@@ -21,59 +19,7 @@ struct CommentPayload {
|
||||
message: String,
|
||||
}
|
||||
|
||||
static BAD_WORDS_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?i)(хуй|хуя|хуе|хул|пизд|ебат|ебан|ебл|ебуч|бляд|блят|шлюх|сука|сучк|жоп|пидор|пидар|педик|голубо|гондон|гандон|залуп|мудак|мудил|мраз|уеб|чмо|долбое|даун|чурк|нигер|нигга|хач|хохол|кацап|жид|порно|наркот|убить|насилие|соли|закладк|мефедрон|гашиш|спайс|heroin|meth|murder|gore|cpam|scam|crypto|binance|bitco)").unwrap()
|
||||
});
|
||||
|
||||
const SAFE_WORDS: &[&str] = &[
|
||||
"колебат",
|
||||
"оскорблят",
|
||||
"употреблят",
|
||||
"расслаблят",
|
||||
"потреблят",
|
||||
"истреблят",
|
||||
"хлебат",
|
||||
"щебет",
|
||||
"мебел",
|
||||
"стебл",
|
||||
"ослабл",
|
||||
"гребл",
|
||||
"хлебл",
|
||||
"дебел",
|
||||
"колебл",
|
||||
"страхуй",
|
||||
"подстрахуй",
|
||||
"рубл",
|
||||
"сабл",
|
||||
"челяб",
|
||||
"влюбл",
|
||||
"ограбл",
|
||||
"дубл",
|
||||
"гибл",
|
||||
"корабл",
|
||||
"джоп",
|
||||
"джобс",
|
||||
"прожора",
|
||||
"скипидар",
|
||||
"купидор",
|
||||
"коридор",
|
||||
"чмок",
|
||||
"ячмен",
|
||||
"черемух",
|
||||
"мразер",
|
||||
"замороз",
|
||||
"мороз",
|
||||
"мрамор",
|
||||
"рисука",
|
||||
"присука",
|
||||
"бамбука",
|
||||
"ожид",
|
||||
"жидкос",
|
||||
"жидк",
|
||||
"даунгрейд",
|
||||
"адаун",
|
||||
];
|
||||
|
||||
// Восстановленный генератор нагрузки для спидтеста (25 MB)
|
||||
static RANDOM_PAYLOAD: LazyLock<Bytes> = LazyLock::new(|| {
|
||||
let mut data = vec![0u8; 25 * 1024 * 1024];
|
||||
let mut state: u32 = 123456789;
|
||||
@@ -84,75 +30,13 @@ static RANDOM_PAYLOAD: LazyLock<Bytes> = LazyLock::new(|| {
|
||||
Bytes::from(data)
|
||||
});
|
||||
|
||||
fn apply_allowlist(text: &str) -> String {
|
||||
let mut safe_text = text.to_string();
|
||||
for &safe_word in SAFE_WORDS {
|
||||
safe_text = safe_text.replace(safe_word, "[SAFE]");
|
||||
}
|
||||
safe_text
|
||||
}
|
||||
|
||||
fn normalize_for_filter(input: &str) -> String {
|
||||
let mut normalized = String::new();
|
||||
let mut prev_char: Option<char> = None;
|
||||
for ch in input.to_lowercase().chars() {
|
||||
if ch.is_whitespace() {
|
||||
normalized.push(' ');
|
||||
prev_char = Some(' ');
|
||||
continue;
|
||||
}
|
||||
if !ch.is_alphanumeric() {
|
||||
continue;
|
||||
}
|
||||
let mapped = match ch {
|
||||
'a' | '@' => 'а',
|
||||
'b' => 'в',
|
||||
'c' => 'с',
|
||||
'e' | '3' => 'е',
|
||||
'o' | '0' => 'о',
|
||||
'p' => 'р',
|
||||
'x' => 'х',
|
||||
'y' => 'у',
|
||||
'm' => 'м',
|
||||
't' => 'т',
|
||||
'k' => 'к',
|
||||
'h' => 'н',
|
||||
'4' => 'ч',
|
||||
_ => ch,
|
||||
};
|
||||
if let Some(p) = prev_char {
|
||||
if p == mapped && mapped != ' ' {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
normalized.push(mapped);
|
||||
prev_char = Some(mapped);
|
||||
}
|
||||
normalized
|
||||
}
|
||||
|
||||
fn is_spam(input: &str) -> bool {
|
||||
let alpha_chars: String = input.chars().filter(|c| c.is_alphabetic()).collect();
|
||||
if alpha_chars.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let uppercase_ratio =
|
||||
alpha_chars.chars().filter(|c| c.is_uppercase()).count() as f64 / alpha_chars.len() as f64;
|
||||
if input.len() > 10 && uppercase_ratio > 0.7 {
|
||||
return true;
|
||||
}
|
||||
input
|
||||
.split_whitespace()
|
||||
.any(|word| word.len() > 35 && !word.starts_with("http"))
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
ai_classifier: ToxicityClassifier,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("🧠 Booting AI Neural Link (Pure Rust Engine)...");
|
||||
println!("🧠 [SYSTEM] Booting AI Neural Link (Pure Rust Engine)...");
|
||||
let classifier = ToxicityClassifier::new("models/model.onnx", "models/tokenizer.json")
|
||||
.expect("CRITICAL: Failed to load Neural Link model");
|
||||
|
||||
@@ -168,7 +52,7 @@ async fn main() {
|
||||
.layer(DefaultBodyLimit::max(100 * 1024 * 1024))
|
||||
.with_state(state);
|
||||
|
||||
println!("🚀 Netrunner Gateway started on 0.0.0.0:3001");
|
||||
println!("🚀 [READY] Netrunner Gateway started on 0.0.0.0:3001");
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
@@ -177,48 +61,90 @@ async fn create_comment(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<CommentPayload>,
|
||||
) -> impl IntoResponse {
|
||||
if is_spam(&payload.message) || is_spam(&payload.nickname) {
|
||||
return (StatusCode::NOT_ACCEPTABLE, "Spam detected.");
|
||||
}
|
||||
if payload.message.is_inappropriate() || payload.nickname.is_inappropriate() {
|
||||
return (StatusCode::NOT_ACCEPTABLE, "Policy violation.");
|
||||
}
|
||||
let message = payload.message.trim().to_string();
|
||||
let nickname = payload.nickname.trim().to_string();
|
||||
|
||||
let normalized_msg = normalize_for_filter(&payload.message);
|
||||
let normalized_nick = normalize_for_filter(&payload.nickname);
|
||||
if BAD_WORDS_REGEX.is_match(&apply_allowlist(&normalized_msg))
|
||||
|| BAD_WORDS_REGEX.is_match(&apply_allowlist(&normalized_nick))
|
||||
{
|
||||
return (StatusCode::NOT_ACCEPTABLE, "Policy violation.");
|
||||
}
|
||||
println!("📩 [INCOMING] '{}': '{}'", nickname, message);
|
||||
|
||||
let message = payload.message.clone();
|
||||
let nickname = payload.nickname.clone();
|
||||
// --- ГВАРД-РЕЙЛЫ (Только для текста сообщения) ---
|
||||
let whitelist = [
|
||||
"vpn",
|
||||
"service",
|
||||
"experience",
|
||||
"use",
|
||||
"working",
|
||||
"fast",
|
||||
"thanks",
|
||||
"привет",
|
||||
"спасибо",
|
||||
"работает",
|
||||
"test",
|
||||
"тест",
|
||||
];
|
||||
let msg_lower = message.to_lowercase();
|
||||
let msg_whitelisted = whitelist.iter().any(|&word| msg_lower.contains(word));
|
||||
let msg_has_alphabetic = message.chars().any(|c| c.is_alphabetic());
|
||||
|
||||
let msg_safe_by_guard = msg_whitelisted || message.len() < 4 || !msg_has_alphabetic;
|
||||
|
||||
// Запускаем проверку
|
||||
let is_toxic = tokio::task::spawn_blocking(move || {
|
||||
state.ai_classifier.is_toxic(&message) || state.ai_classifier.is_toxic(&nickname)
|
||||
// 1. ВСЕГДА проверяем никнейм
|
||||
let is_nick_toxic = state.ai_classifier.is_toxic(&nickname);
|
||||
|
||||
// 2. Проверяем сообщение, если оно не попало под гвард-рейлы
|
||||
let is_msg_toxic = if msg_safe_by_guard {
|
||||
println!(
|
||||
"🛡️ [GUARD RAILS] Message text bypassed (whitelisted, short, or non-alphabetic)."
|
||||
);
|
||||
false
|
||||
} else {
|
||||
state.ai_classifier.is_toxic(&message)
|
||||
};
|
||||
|
||||
is_nick_toxic || is_msg_toxic
|
||||
})
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_toxic {
|
||||
println!(
|
||||
"🚫 [REJECTED] Blocked by AI: nick='{}', msg='{}'",
|
||||
payload.nickname, payload.message
|
||||
);
|
||||
return (StatusCode::NOT_ACCEPTABLE, "Behavioral policy violation.");
|
||||
}
|
||||
|
||||
println!("✅ [PASSED] Forwarding to PocketBase...");
|
||||
|
||||
let pb_url = format!(
|
||||
"{}/api/collections/comments/records",
|
||||
std::env::var("PB_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8090".to_string())
|
||||
std::env::var("PB_INTERNAL_URL")
|
||||
.unwrap_or_else(|_| "http://netrunner-cms:8090".to_string())
|
||||
);
|
||||
|
||||
let client = Client::new();
|
||||
match client.post(pb_url).json(&payload).send().await {
|
||||
Ok(res) if res.status().is_success() => (StatusCode::OK, "Delivered."),
|
||||
_ => (StatusCode::BAD_REQUEST, "Uplink offline."),
|
||||
match client.post(&pb_url).json(&payload).send().await {
|
||||
Ok(res) => {
|
||||
if res.status().is_success() {
|
||||
println!("🎉 [SUCCESS] Saved to PocketBase");
|
||||
(StatusCode::OK, "Delivered.")
|
||||
} else {
|
||||
println!("❌ [ERROR] PocketBase rejected: {}", res.status());
|
||||
(StatusCode::BAD_REQUEST, "Uplink rejected.")
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("💥 [CRITICAL] PocketBase unreachable: {}", e);
|
||||
(StatusCode::SERVICE_UNAVAILABLE, "Uplink offline.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn speedtest_download() -> impl IntoResponse {
|
||||
println!("📊 [SPEEDTEST] Download requested");
|
||||
let body = RANDOM_PAYLOAD.clone();
|
||||
let len = body.len();
|
||||
|
||||
(
|
||||
[
|
||||
(
|
||||
@@ -237,5 +163,6 @@ async fn speedtest_download() -> impl IntoResponse {
|
||||
}
|
||||
|
||||
async fn speedtest_upload(body: Bytes) -> impl IntoResponse {
|
||||
println!("📊 [SPEEDTEST] Upload received: {} bytes", body.len());
|
||||
(StatusCode::OK, format!("Received {} bytes", body.len()))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user