Fix chat session management and add floating widget

- Fix session sync: events now send complete ChatSession data instead of
  partial ChatSessionSummary, ensuring proper ticket/agent info display
- Add session-ended event detection to remove closed sessions from client
- Add ChatFloatingWidget component for in-app chat experience
- Restrict endSession to ADMIN/MANAGER/AGENT roles only
- Improve polling logic to detect new and ended sessions properly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-07 11:16:56 -03:00
parent e4f8f465de
commit 88a3b37f2f
5 changed files with 680 additions and 92 deletions

View file

@ -322,102 +322,131 @@ impl ChatRuntime {
Ok(result) => {
last_checked_at = Some(chrono::Utc::now().timestamp_millis());
if result.has_active_sessions {
// Verificar novas sessoes
let prev_sessions: Vec<String> = {
last_sessions.lock().iter().map(|s| s.session_id.clone()).collect()
};
// Buscar sessoes completas para ter dados corretos
let current_sessions = if result.has_active_sessions {
fetch_sessions(&base_clone, &token_clone).await.unwrap_or_default()
} else {
Vec::new()
};
// Buscar detalhes das sessoes
if let Ok(sessions) = fetch_sessions(&base_clone, &token_clone).await {
for session in &sessions {
if !prev_sessions.contains(&session.session_id) {
// Nova sessao! Emitir evento
crate::log_info!(
"Nova sessao de chat: ticket={}",
session.ticket_id
);
let _ = app.emit(
"raven://chat/session-started",
SessionStartedEvent {
session: session.clone(),
},
);
// Verificar sessoes anteriores
let prev_sessions: Vec<ChatSession> = last_sessions.lock().clone();
let prev_session_ids: Vec<String> = prev_sessions.iter().map(|s| s.session_id.clone()).collect();
let current_session_ids: Vec<String> = current_sessions.iter().map(|s| s.session_id.clone()).collect();
// Enviar notificacao nativa do Windows
// A janela de chat NAO abre automaticamente -
// o usuario deve clicar na notificacao ou no tray
let notification_title = format!(
"Chat iniciado - Chamado #{}",
session.ticket_ref
);
let notification_body = format!(
"{} iniciou um chat de suporte.\nClique no icone do Raven para abrir.",
session.agent_name
);
if let Err(e) = app
.notification()
.builder()
.title(&notification_title)
.body(&notification_body)
.show()
{
crate::log_warn!(
"Falha ao enviar notificacao: {e}"
);
}
}
}
// Atualizar cache
*last_sessions.lock() = sessions;
}
// Verificar mensagens nao lidas e emitir evento
let prev_unread = *last_unread_count.lock();
let new_messages = result.total_unread > prev_unread;
*last_unread_count.lock() = result.total_unread;
if result.total_unread > 0 {
// Detectar novas sessoes
for session in &current_sessions {
if !prev_session_ids.contains(&session.session_id) {
// Nova sessao! Emitir evento
crate::log_info!(
"Chat: {} mensagens nao lidas (prev={})",
result.total_unread,
prev_unread
"Nova sessao de chat: ticket={}, session={}",
session.ticket_id,
session.session_id
);
let _ = app.emit(
"raven://chat/unread-update",
serde_json::json!({
"totalUnread": result.total_unread,
"sessions": result.sessions
}),
"raven://chat/session-started",
SessionStartedEvent {
session: session.clone(),
},
);
// Notificar novas mensagens (apenas se aumentou)
if new_messages && prev_unread > 0 {
let new_count = result.total_unread - prev_unread;
let notification_title = "Nova mensagem de suporte";
let notification_body = if new_count == 1 {
"Voce recebeu 1 nova mensagem no chat".to_string()
} else {
format!("Voce recebeu {} novas mensagens no chat", new_count)
};
if let Err(e) = app
.notification()
.builder()
.title(notification_title)
.body(&notification_body)
.show()
{
crate::log_warn!(
"Falha ao enviar notificacao de nova mensagem: {e}"
);
}
// NAO foca a janela automaticamente - usuario abre manualmente
// Enviar notificacao nativa do Windows
let notification_title = format!(
"Chat iniciado - Chamado #{}",
session.ticket_ref
);
let notification_body = format!(
"{} iniciou um chat de suporte.\nClique no icone do Raven para abrir.",
session.agent_name
);
if let Err(e) = app
.notification()
.builder()
.title(&notification_title)
.body(&notification_body)
.show()
{
crate::log_warn!(
"Falha ao enviar notificacao de nova sessao: {e}"
);
}
}
} else {
// Sem sessoes ativas
*last_sessions.lock() = Vec::new();
}
// Detectar sessoes encerradas
for prev_session in &prev_sessions {
if !current_session_ids.contains(&prev_session.session_id) {
// Sessao foi encerrada! Emitir evento
crate::log_info!(
"Sessao de chat encerrada: ticket={}, session={}",
prev_session.ticket_id,
prev_session.session_id
);
let _ = app.emit(
"raven://chat/session-ended",
serde_json::json!({
"sessionId": prev_session.session_id,
"ticketId": prev_session.ticket_id
}),
);
}
}
// Atualizar cache de sessoes
*last_sessions.lock() = current_sessions.clone();
// Verificar mensagens nao lidas
let prev_unread = *last_unread_count.lock();
let new_messages = result.total_unread > prev_unread;
*last_unread_count.lock() = result.total_unread;
// Sempre emitir unread-update com sessoes completas
let _ = app.emit(
"raven://chat/unread-update",
serde_json::json!({
"totalUnread": result.total_unread,
"sessions": current_sessions
}),
);
// Notificar novas mensagens (quando aumentou)
if new_messages && result.total_unread > 0 {
let new_count = result.total_unread - prev_unread;
crate::log_info!(
"Chat: {} novas mensagens (total={})",
new_count,
result.total_unread
);
// Emitir evento para o frontend atualizar UI
let _ = app.emit(
"raven://chat/new-message",
serde_json::json!({
"totalUnread": result.total_unread,
"newCount": new_count,
"sessions": current_sessions
}),
);
// Enviar notificacao nativa do Windows
let notification_title = "Nova mensagem de suporte";
let notification_body = if new_count == 1 {
"Voce recebeu 1 nova mensagem no chat".to_string()
} else {
format!("Voce recebeu {} novas mensagens no chat", new_count)
};
if let Err(e) = app
.notification()
.builder()
.title(notification_title)
.body(&notification_body)
.show()
{
crate::log_warn!(
"Falha ao enviar notificacao de nova mensagem: {e}"
);
}
}
}
Err(e) => {