fix(chat): melhora confiabilidade da deteccao de novas mensagens
- Implementa deteccao dual: timestamp (lastActivityAt) + contador - Adiciona persistencia de estado em ~/.local/share/Raven/chat-state.json - Corrige race condition no servidor com refetch antes do patch - Adiciona campo lastAgentMessageAt no schema do Convex - Adiciona logs de diagnostico detalhados 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c4664ab1c7
commit
2293a0275a
5 changed files with 310 additions and 30 deletions
113
apps/desktop/src-tauri/Cargo.lock
generated
113
apps/desktop/src-tauri/Cargo.lock
generated
|
|
@ -63,6 +63,7 @@ dependencies = [
|
|||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"convex",
|
||||
"dirs 5.0.1",
|
||||
"futures-util",
|
||||
"get_if_addrs",
|
||||
"hostname",
|
||||
|
|
@ -938,13 +939,34 @@ dependencies = [
|
|||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
"dirs-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.4.6",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -955,7 +977,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
|
|
@ -3629,6 +3651,17 @@ dependencies = [
|
|||
"bitflags 2.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
|
|
@ -4516,7 +4549,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"dunce",
|
||||
"embed_plist",
|
||||
"getrandom 0.3.3",
|
||||
|
|
@ -4566,7 +4599,7 @@ checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"glob",
|
||||
"heck 0.5.0",
|
||||
"json-patch",
|
||||
|
|
@ -4788,7 +4821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "27cbc31740f4d507712550694749572ec0e43bdd66992db7599b89fbfd6b167b"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"http",
|
||||
|
|
@ -5324,7 +5357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"libappindicator",
|
||||
"muda",
|
||||
"objc2 0.6.3",
|
||||
|
|
@ -6105,6 +6138,15 @@ dependencies = [
|
|||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
@ -6156,6 +6198,21 @@ dependencies = [
|
|||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6213,6 +6270,12 @@ version = "0.42.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6231,6 +6294,12 @@ version = "0.42.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6249,6 +6318,12 @@ version = "0.42.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6279,6 +6354,12 @@ version = "0.42.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6297,6 +6378,12 @@ version = "0.42.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6315,6 +6402,12 @@ version = "0.42.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6333,6 +6426,12 @@ version = "0.42.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
@ -6395,7 +6494,7 @@ dependencies = [
|
|||
"block2 0.6.2",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"dirs 6.0.0",
|
||||
"dpi",
|
||||
"dunce",
|
||||
"gdkx11",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ base64 = "0.22"
|
|||
sha2 = "0.10"
|
||||
convex = "0.10.2"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
dirs = "5"
|
||||
# SSE usa reqwest com stream, nao precisa de websocket
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ use parking_lot::Mutex;
|
|||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use tauri::async_runtime::JoinHandle;
|
||||
use tauri::{Emitter, Manager, WebviewWindowBuilder, WebviewUrl};
|
||||
use tauri_plugin_notification::NotificationExt;
|
||||
|
|
@ -100,6 +102,77 @@ pub struct SessionStartedEvent {
|
|||
pub session: ChatSession,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PERSISTENCIA DE ESTADO
|
||||
// ============================================================================
|
||||
|
||||
/// Estado persistido do chat para sobreviver a restarts
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ChatPersistedState {
|
||||
last_unread_count: u32,
|
||||
sessions: Vec<ChatSession>,
|
||||
saved_at: u64, // Unix timestamp em ms
|
||||
}
|
||||
|
||||
const STATE_FILE_NAME: &str = "chat-state.json";
|
||||
const STATE_MAX_AGE_MS: u64 = 3600_000; // 1 hora - ignorar estados mais antigos
|
||||
|
||||
fn get_state_file_path() -> Option<PathBuf> {
|
||||
dirs::data_local_dir().map(|p| p.join("Raven").join(STATE_FILE_NAME))
|
||||
}
|
||||
|
||||
fn save_chat_state(last_unread: u32, sessions: &[ChatSession]) {
|
||||
let Some(path) = get_state_file_path() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Criar diretorio se nao existir
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = fs::create_dir_all(parent);
|
||||
}
|
||||
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_millis() as u64)
|
||||
.unwrap_or(0);
|
||||
|
||||
let state = ChatPersistedState {
|
||||
last_unread_count: last_unread,
|
||||
sessions: sessions.to_vec(),
|
||||
saved_at: now,
|
||||
};
|
||||
|
||||
if let Ok(json) = serde_json::to_string_pretty(&state) {
|
||||
let _ = fs::write(&path, json);
|
||||
crate::log_info!("[CHAT] Estado persistido: unread={}, sessions={}", last_unread, sessions.len());
|
||||
}
|
||||
}
|
||||
|
||||
fn load_chat_state() -> Option<ChatPersistedState> {
|
||||
let path = get_state_file_path()?;
|
||||
|
||||
let json = fs::read_to_string(&path).ok()?;
|
||||
let state: ChatPersistedState = serde_json::from_str(&json).ok()?;
|
||||
|
||||
// Verificar se estado nao esta muito antigo
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_millis() as u64)
|
||||
.unwrap_or(0);
|
||||
|
||||
if now.saturating_sub(state.saved_at) > STATE_MAX_AGE_MS {
|
||||
crate::log_info!("[CHAT] Estado persistido ignorado (muito antigo)");
|
||||
return None;
|
||||
}
|
||||
|
||||
crate::log_info!(
|
||||
"[CHAT] Estado restaurado: unread={}, sessions={}",
|
||||
state.last_unread_count, state.sessions.len()
|
||||
);
|
||||
Some(state)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HTTP CLIENT
|
||||
// ============================================================================
|
||||
|
|
@ -462,10 +535,16 @@ pub struct ChatRuntime {
|
|||
|
||||
impl ChatRuntime {
|
||||
pub fn new() -> Self {
|
||||
// Tentar restaurar estado persistido
|
||||
let (sessions, unread) = match load_chat_state() {
|
||||
Some(state) => (state.sessions, state.last_unread_count),
|
||||
None => (Vec::new(), 0),
|
||||
};
|
||||
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(None)),
|
||||
last_sessions: Arc::new(Mutex::new(Vec::new())),
|
||||
last_unread_count: Arc::new(Mutex::new(0)),
|
||||
last_sessions: Arc::new(Mutex::new(sessions)),
|
||||
last_unread_count: Arc::new(Mutex::new(unread)),
|
||||
is_connected: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
|
@ -510,7 +589,9 @@ impl ChatRuntime {
|
|||
let is_connected = self.is_connected.clone();
|
||||
|
||||
let join_handle = tauri::async_runtime::spawn(async move {
|
||||
crate::log_info!("Chat iniciando (Convex realtime + fallback por polling)");
|
||||
crate::log_info!("[CHAT DEBUG] Iniciando sistema de chat");
|
||||
crate::log_info!("[CHAT DEBUG] Convex URL: {}", convex_clone);
|
||||
crate::log_info!("[CHAT DEBUG] API Base URL: {}", base_clone);
|
||||
|
||||
let mut backoff_ms: u64 = 1_000;
|
||||
let max_backoff_ms: u64 = 30_000;
|
||||
|
|
@ -522,12 +603,16 @@ impl ChatRuntime {
|
|||
break;
|
||||
}
|
||||
|
||||
crate::log_info!("[CHAT DEBUG] Tentando conectar ao Convex...");
|
||||
let client_result = ConvexClient::new(&convex_clone).await;
|
||||
let mut client = match client_result {
|
||||
Ok(c) => c,
|
||||
Ok(c) => {
|
||||
crate::log_info!("[CHAT DEBUG] Cliente Convex criado com sucesso");
|
||||
c
|
||||
}
|
||||
Err(err) => {
|
||||
is_connected.store(false, Ordering::Relaxed);
|
||||
crate::log_warn!("Falha ao criar cliente Convex: {err:?}");
|
||||
crate::log_warn!("[CHAT DEBUG] FALHA ao criar cliente Convex: {err:?}");
|
||||
|
||||
if last_poll.elapsed() >= poll_interval {
|
||||
poll_and_process_chat_update(
|
||||
|
|
@ -550,16 +635,18 @@ impl ChatRuntime {
|
|||
let mut args = BTreeMap::new();
|
||||
args.insert("machineToken".to_string(), token_clone.clone().into());
|
||||
|
||||
crate::log_info!("[CHAT DEBUG] Assinando liveChat:checkMachineUpdates...");
|
||||
let subscribe_result = client.subscribe("liveChat:checkMachineUpdates", args).await;
|
||||
let mut subscription = match subscribe_result {
|
||||
Ok(sub) => {
|
||||
is_connected.store(true, Ordering::Relaxed);
|
||||
backoff_ms = 1_000;
|
||||
crate::log_info!("[CHAT DEBUG] CONECTADO ao Convex WebSocket com sucesso!");
|
||||
sub
|
||||
}
|
||||
Err(err) => {
|
||||
is_connected.store(false, Ordering::Relaxed);
|
||||
crate::log_warn!("Falha ao assinar liveChat:checkMachineUpdates: {err:?}");
|
||||
crate::log_warn!("[CHAT DEBUG] FALHA ao assinar checkMachineUpdates: {err:?}");
|
||||
|
||||
if last_poll.elapsed() >= poll_interval {
|
||||
poll_and_process_chat_update(
|
||||
|
|
@ -579,8 +666,12 @@ impl ChatRuntime {
|
|||
}
|
||||
};
|
||||
|
||||
crate::log_info!("[CHAT DEBUG] Entrando no loop de escuta WebSocket...");
|
||||
let mut update_count: u64 = 0;
|
||||
while let Some(next) = subscription.next().await {
|
||||
update_count += 1;
|
||||
if stop_clone.load(Ordering::Relaxed) {
|
||||
crate::log_info!("[CHAT DEBUG] Stop flag detectado, saindo do loop");
|
||||
break;
|
||||
}
|
||||
match next {
|
||||
|
|
@ -601,6 +692,11 @@ impl ChatRuntime {
|
|||
})
|
||||
.unwrap_or(0);
|
||||
|
||||
crate::log_info!(
|
||||
"[CHAT DEBUG] UPDATE #{} recebido via WebSocket: hasActive={}, totalUnread={}",
|
||||
update_count, has_active, total_unread
|
||||
);
|
||||
|
||||
process_chat_update(
|
||||
&base_clone,
|
||||
&token_clone,
|
||||
|
|
@ -613,13 +709,13 @@ impl ChatRuntime {
|
|||
.await;
|
||||
}
|
||||
FunctionResult::ConvexError(err) => {
|
||||
crate::log_warn!("Convex error em checkMachineUpdates: {err:?}");
|
||||
crate::log_warn!("[CHAT DEBUG] Convex error em checkMachineUpdates: {err:?}");
|
||||
}
|
||||
FunctionResult::ErrorMessage(msg) => {
|
||||
crate::log_warn!("Erro em checkMachineUpdates: {msg}");
|
||||
crate::log_warn!("[CHAT DEBUG] Erro em checkMachineUpdates: {msg}");
|
||||
}
|
||||
FunctionResult::Value(other) => {
|
||||
crate::log_warn!("Payload inesperado em checkMachineUpdates: {other:?}");
|
||||
crate::log_warn!("[CHAT DEBUG] Payload inesperado em checkMachineUpdates: {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -627,10 +723,11 @@ impl ChatRuntime {
|
|||
is_connected.store(false, Ordering::Relaxed);
|
||||
|
||||
if stop_clone.load(Ordering::Relaxed) {
|
||||
crate::log_info!("[CHAT DEBUG] Stop flag detectado apos loop");
|
||||
break;
|
||||
}
|
||||
|
||||
crate::log_warn!("Chat realtime desconectado; aplicando fallback e tentando reconectar");
|
||||
crate::log_warn!("[CHAT DEBUG] WebSocket DESCONECTADO! Aplicando fallback e tentando reconectar...");
|
||||
if last_poll.elapsed() >= poll_interval {
|
||||
poll_and_process_chat_update(
|
||||
&base_clone,
|
||||
|
|
@ -684,8 +781,13 @@ async fn poll_and_process_chat_update(
|
|||
last_sessions: &Arc<Mutex<Vec<ChatSession>>>,
|
||||
last_unread_count: &Arc<Mutex<u32>>,
|
||||
) {
|
||||
crate::log_info!("[CHAT DEBUG] Executando fallback HTTP polling...");
|
||||
match poll_chat_updates(base_url, token, None).await {
|
||||
Ok(result) => {
|
||||
crate::log_info!(
|
||||
"[CHAT DEBUG] Polling OK: hasActive={}, totalUnread={}",
|
||||
result.has_active_sessions, result.total_unread
|
||||
);
|
||||
process_chat_update(
|
||||
base_url,
|
||||
token,
|
||||
|
|
@ -698,7 +800,7 @@ async fn poll_and_process_chat_update(
|
|||
.await;
|
||||
}
|
||||
Err(err) => {
|
||||
crate::log_warn!("Chat fallback poll falhou: {err}");
|
||||
crate::log_warn!("[CHAT DEBUG] Fallback poll FALHOU: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -712,10 +814,18 @@ async fn process_chat_update(
|
|||
has_active_sessions: bool,
|
||||
total_unread: u32,
|
||||
) {
|
||||
crate::log_info!(
|
||||
"[CHAT DEBUG] process_chat_update: hasActive={}, totalUnread={}",
|
||||
has_active_sessions, total_unread
|
||||
);
|
||||
|
||||
// Buscar sessoes completas para ter dados corretos
|
||||
let mut current_sessions = if has_active_sessions {
|
||||
fetch_sessions(base_url, token).await.unwrap_or_default()
|
||||
let sessions = fetch_sessions(base_url, token).await.unwrap_or_default();
|
||||
crate::log_info!("[CHAT DEBUG] Buscou {} sessoes ativas", sessions.len());
|
||||
sessions
|
||||
} else {
|
||||
crate::log_info!("[CHAT DEBUG] Sem sessoes ativas");
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
|
|
@ -776,14 +886,58 @@ async fn process_chat_update(
|
|||
}
|
||||
}
|
||||
|
||||
// Atualizar cache de sessoes
|
||||
*last_sessions.lock() = current_sessions.clone();
|
||||
// =========================================================================
|
||||
// DETECCAO ROBUSTA DE NOVAS MENSAGENS
|
||||
// Usa DUAS estrategias: timestamp E contador (belt and suspenders)
|
||||
// =========================================================================
|
||||
|
||||
// Verificar mensagens nao lidas
|
||||
let prev_unread = *last_unread_count.lock();
|
||||
let new_messages = total_unread > prev_unread;
|
||||
|
||||
// Estrategia 1: Detectar por lastActivityAt de cada sessao
|
||||
// Se alguma sessao teve atividade mais recente E tem mensagens nao lidas -> nova mensagem
|
||||
let mut detected_by_activity = false;
|
||||
let mut activity_details = String::new();
|
||||
|
||||
for session in ¤t_sessions {
|
||||
let prev_activity = prev_sessions
|
||||
.iter()
|
||||
.find(|s| s.session_id == session.session_id)
|
||||
.map(|s| s.last_activity_at)
|
||||
.unwrap_or(0);
|
||||
|
||||
// Se lastActivityAt aumentou E ha mensagens nao lidas -> nova mensagem do agente
|
||||
if session.last_activity_at > prev_activity && session.unread_count > 0 {
|
||||
detected_by_activity = true;
|
||||
activity_details = format!(
|
||||
"sessao={} activity: {} -> {} unread={}",
|
||||
session.ticket_id, prev_activity, session.last_activity_at, session.unread_count
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Estrategia 2: Fallback por contador total (metodo original)
|
||||
let detected_by_count = total_unread > prev_unread;
|
||||
|
||||
// Nova mensagem se QUALQUER estrategia detectar
|
||||
let new_messages = detected_by_activity || detected_by_count;
|
||||
|
||||
// Log detalhado para diagnostico
|
||||
crate::log_info!(
|
||||
"[CHAT] Deteccao: by_activity={} by_count={} (prev={} curr={}) resultado={}",
|
||||
detected_by_activity, detected_by_count, prev_unread, total_unread, new_messages
|
||||
);
|
||||
if detected_by_activity {
|
||||
crate::log_info!("[CHAT] Detectado por atividade: {}", activity_details);
|
||||
}
|
||||
|
||||
// Atualizar caches APOS deteccao (importante: manter ordem)
|
||||
*last_sessions.lock() = current_sessions.clone();
|
||||
*last_unread_count.lock() = total_unread;
|
||||
|
||||
// Persistir estado para sobreviver a restarts
|
||||
save_chat_state(total_unread, ¤t_sessions);
|
||||
|
||||
// Sempre emitir unread-update
|
||||
let _ = app.emit(
|
||||
"raven://chat/unread-update",
|
||||
|
|
@ -795,9 +949,17 @@ async fn process_chat_update(
|
|||
|
||||
// Notificar novas mensagens - mostrar chat minimizado com badge
|
||||
if new_messages && total_unread > 0 {
|
||||
let new_count = total_unread - prev_unread;
|
||||
let new_count = if total_unread > prev_unread {
|
||||
total_unread - prev_unread
|
||||
} else {
|
||||
1 // Se detectou por activity mas contador nao mudou, assumir 1 nova
|
||||
};
|
||||
|
||||
crate::log_info!("Chat: {} novas mensagens (total={})", new_count, total_unread);
|
||||
crate::log_info!(
|
||||
"[CHAT] NOVAS MENSAGENS! count={}, total={}, metodo={}",
|
||||
new_count, total_unread,
|
||||
if detected_by_activity { "activity" } else { "count" }
|
||||
);
|
||||
|
||||
let _ = app.emit(
|
||||
"raven://chat/new-message",
|
||||
|
|
@ -885,6 +1047,16 @@ async fn process_chat_update(
|
|||
.title(notification_title)
|
||||
.body(¬ification_body)
|
||||
.show();
|
||||
} else {
|
||||
// Log para debug quando NAO ha novas mensagens
|
||||
if total_unread == 0 {
|
||||
crate::log_info!("[CHAT DEBUG] Sem mensagens nao lidas (total=0)");
|
||||
} else if !new_messages {
|
||||
crate::log_info!(
|
||||
"[CHAT DEBUG] Sem novas mensagens (prev={} >= total={})",
|
||||
prev_unread, total_unread
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -478,6 +478,7 @@ export default defineSchema({
|
|||
startedAt: v.number(),
|
||||
endedAt: v.optional(v.number()),
|
||||
lastActivityAt: v.number(),
|
||||
lastAgentMessageAt: v.optional(v.number()), // Timestamp da ultima mensagem do agente (para deteccao confiavel)
|
||||
unreadByMachine: v.optional(v.number()),
|
||||
unreadByAgent: v.optional(v.number()),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3734,6 +3734,8 @@ export const postChatMessage = mutation({
|
|||
await ctx.db.patch(ticketId, { updatedAt: now })
|
||||
|
||||
// Se o autor for um agente (ADMIN, MANAGER, AGENT), incrementar unreadByMachine na sessao de chat ativa
|
||||
// IMPORTANTE: Buscar sessao IMEDIATAMENTE antes do patch para evitar race conditions
|
||||
// O Convex faz retry automatico em caso de OCC conflict
|
||||
const actorRole = participant.role?.toUpperCase() ?? ""
|
||||
if (["ADMIN", "MANAGER", "AGENT"].includes(actorRole)) {
|
||||
const activeSession = await ctx.db
|
||||
|
|
@ -3743,10 +3745,15 @@ export const postChatMessage = mutation({
|
|||
.first()
|
||||
|
||||
if (activeSession) {
|
||||
await ctx.db.patch(activeSession._id, {
|
||||
unreadByMachine: (activeSession.unreadByMachine ?? 0) + 1,
|
||||
lastActivityAt: now,
|
||||
})
|
||||
// Refetch para garantir valor mais recente (OCC protection)
|
||||
const freshSession = await ctx.db.get(activeSession._id)
|
||||
if (freshSession) {
|
||||
await ctx.db.patch(activeSession._id, {
|
||||
unreadByMachine: (freshSession.unreadByMachine ?? 0) + 1,
|
||||
lastActivityAt: now,
|
||||
lastAgentMessageAt: now, // Novo: timestamp da ultima mensagem do agente
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue