feat: add emoji-regex and remove-accents dependencies; enhance chat and setup pages

- Added `emoji-regex` and `remove-accents` packages to improve text processing capabilities.
- Implemented automatic Ollama startup in the chat page for better user experience.
- Enhanced setup page to include GPU selection based on hardware specifications.
- Updated chat input to support real-time validation and improved user feedback.
- Refactored message handling in chat to incorporate new processing features.
This commit is contained in:
Evandro Fonseca Junior committed 2025-11-26 16:12:16 -03:00
1 parent 4275295e4f
commit 84ea2dec19
51 files changed
+7857 -573

No files matched your search

+158 -1
View File
@@ -1,5 +1,5 @@
use anyhow::Result;
use headless_chrome::{Browser, LaunchOptions};
use headless_chrome::{Browser, LaunchOptions, Tab};
use reqwest::header::USER_AGENT;
use scraper::{Html, Selector};
use std::sync::Arc;
@@ -715,6 +715,21 @@ fn fetch_and_convert_sync(browser: &Browser, url: &str) -> Result<ScrapedContent
return Err(anyhow::anyhow!("Timeout ao processar página"));
}
// Injetar script para bloquear autoplay de mídia
// Isso garante que nenhum vídeo/áudio seja reproduzido durante o scraping
match disable_media_autoplay(&tab) {
Ok(_) => {
log::debug!("Autoplay de mídia bloqueado para: {}", url);
}
Err(e) => {
log::warn!("Aviso: Falha ao bloquear autoplay em {}: {}", url, e);
// Não falhar o scraping por causa disso, apenas logar
}
}
// Aguardar pequeno delay para garantir que script foi executado
std::thread::sleep(Duration::from_millis(100));
// Extrair HTML renderizado
let content = match tab.get_content() {
Ok(c) => c,
@@ -773,10 +788,152 @@ fn fetch_and_convert_sync(browser: &Browser, url: &str) -> Result<ScrapedContent
}
}
/// Desabilita autoplay de mídia injetando JavaScript na página
/// Esta função pausa todos os elementos de vídeo/áudio e previne autoplay
fn disable_media_autoplay(tab: &Tab) -> Result<()> {
let script = r#"
(function() {
// Função para pausar todos os elementos de mídia
const pauseAllMedia = () => {
const mediaElements = document.querySelectorAll('video, audio');
let pausedCount = 0;
mediaElements.forEach(media => {
if (!media.paused) {
media.pause();
pausedCount++;
}
// Remover atributo autoplay
media.removeAttribute('autoplay');
media.autoplay = false;
// Silenciar mídia
media.muted = true;
media.volume = 0;
});
return pausedCount;
};
// Executar imediatamente
const initialPaused = pauseAllMedia();
// Observar mudanças no DOM para novos elementos de mídia
const observer = new MutationObserver(() => {
pauseAllMedia();
});
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// Prevenir autoplay interceptando o método play()
const originalPlay = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function() {
// Bloquear play se página não estiver em foco ou estiver oculta
if (document.hidden || !document.hasFocus()) {
return Promise.reject(new Error('Autoplay blocked by scraper'));
}
// Pausar imediatamente após tentativa de play
const result = originalPlay.call(this);
if (result && typeof result.then === 'function') {
result.then(() => {
this.pause();
this.muted = true;
}).catch(() => {});
} else {
this.pause();
this.muted = true;
}
return Promise.reject(new Error('Autoplay blocked'));
};
// Pausar quando página perder foco
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
pauseAllMedia();
}
});
// Bloquear Web Audio API
if (window.AudioContext) {
const OriginalAudioContext = window.AudioContext;
window.AudioContext = function() {
console.warn('AudioContext blocked by scraper');
return null;
};
window.AudioContext.prototype = OriginalAudioContext.prototype;
}
if (window.webkitAudioContext) {
const OriginalWebkitAudioContext = window.webkitAudioContext;
window.webkitAudioContext = function() {
console.warn('webkitAudioContext blocked by scraper');
return null;
};
window.webkitAudioContext.prototype = OriginalWebkitAudioContext.prototype;
}
// Bloquear mídia em iframes também
const iframes = document.querySelectorAll('iframe');
iframes.forEach(iframe => {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc) {
const iframeMedia = iframeDoc.querySelectorAll('video, audio');
iframeMedia.forEach(media => {
media.pause();
media.muted = true;
media.volume = 0;
media.removeAttribute('autoplay');
});
}
} catch (e) {
// Ignorar erros de cross-origin
}
});
// Retornar contagem para logging
return initialPaused;
})();
"#;
match tab.evaluate(script, false) {
Ok(result) => {
// Tentar extrair contagem de elementos pausados do resultado
if let Some(count) = result.value {
log::info!("Script de bloqueio de mídia injetado: {} elementos pausados", count);
} else {
log::debug!("Script de bloqueio de mídia injetado com sucesso");
}
Ok(())
}
Err(e) => {
log::warn!("Erro ao injetar script de bloqueio de mídia: {}", e);
// Não falhar o scraping por causa disso, apenas logar
Ok(())
}
}
}
/// Cria uma instância do Browser (singleton para reutilização)
pub fn create_browser() -> Result<Browser> {
use std::ffi::OsStr;
// Argumentos do Chrome para bloquear autoplay de mídia
// Nota: O bloqueio principal será feito via JavaScript injection, mas esses args ajudam
let chrome_args: Vec<&OsStr> = vec![
OsStr::new("--autoplay-policy=document-user-activation-required"), // Exige interação do usuário para autoplay
OsStr::new("--disable-background-media-playback"), // Desabilita reprodução de mídia em segundo plano
OsStr::new("--mute-audio"), // Silencia todo áudio (mais agressivo, mas garante silêncio)
OsStr::new("--disable-features=AutoplayIgnoreWebAudio"), // Desabilita autoplay de Web Audio
];
let options = LaunchOptions {
headless: true,
args: chrome_args,
..Default::default()
};