feat(desktop): adiciona hub de chats para multiplas sessoes

- Cria ChatSessionList, ChatSessionItem e ChatHubWidget no desktop
- Adiciona comandos Rust para gerenciar hub window
- Quando ha multiplas sessoes, abre hub ao inves de janela individual
- Hub lista todas as sessoes ativas com badge de nao lidos
- Clicar em sessao abre/foca janela de chat especifica
- Menu do tray abre hub quando ha multiplas sessoes

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rever-tecnologia 2025-12-15 12:13:47 -03:00
parent 95ab1b5f0c
commit 29fbbfaa26
6 changed files with 560 additions and 38 deletions

View file

@ -1000,37 +1000,58 @@ async fn process_chat_update(
}
}
// Fallback: se nao conseguimos detectar delta, pega a sessao com mais unread e mais recente.
let session_to_show = if best_delta > 0 {
best_session
} else {
current_sessions.iter().max_by(|a, b| {
a.unread_count
.cmp(&b.unread_count)
.then_with(|| a.last_activity_at.cmp(&b.last_activity_at))
})
};
// Mostrar janela de chat (se nao existe, cria minimizada; se existe, apenas mostra)
if let Some(session) = session_to_show {
let label = format!("chat-{}", session.ticket_id);
if let Some(window) = app.get_webview_window(&label) {
// Janela ja existe - apenas mostrar (NAO minimizar se estiver expandida)
// Isso permite que o usuario mantenha o chat aberto enquanto recebe mensagens
let _ = window.show();
// Verificar se esta expandida (altura > 100px significa expandido)
// Se estiver expandida, NAO minimizar - usuario esta usando o chat
if let Ok(size) = window.inner_size() {
let is_expanded = size.height > 100;
if !is_expanded {
// Janela esta minimizada, manter minimizada
let _ = set_chat_minimized(app, &session.ticket_id, true);
}
// Se esta expandida, nao faz nada - deixa o usuario continuar usando
}
// Se ha multiplas sessoes ativas, usar o hub; senao, abrir janela do chat individual
if current_sessions.len() > 1 {
// Multiplas sessoes - usar hub window
if app.get_webview_window(HUB_WINDOW_LABEL).is_none() {
// Hub nao existe - criar minimizado
let _ = open_hub_window(app);
} else {
// Criar nova janela ja minimizada (menos intrusivo)
let _ = open_chat_window(app, &session.ticket_id, session.ticket_ref);
// Hub ja existe - verificar se esta minimizado
if let Some(hub) = app.get_webview_window(HUB_WINDOW_LABEL) {
let _ = hub.show();
if let Ok(size) = hub.inner_size() {
if size.height < 100 {
// Esta minimizado, manter assim
let _ = set_hub_minimized(app, true);
}
}
}
}
} else {
// Uma sessao - abrir janela individual
// Fallback: se nao conseguimos detectar delta, pega a sessao com mais unread e mais recente.
let session_to_show = if best_delta > 0 {
best_session
} else {
current_sessions.iter().max_by(|a, b| {
a.unread_count
.cmp(&b.unread_count)
.then_with(|| a.last_activity_at.cmp(&b.last_activity_at))
})
};
// Mostrar janela de chat (se nao existe, cria minimizada; se existe, apenas mostra)
if let Some(session) = session_to_show {
let label = format!("chat-{}", session.ticket_id);
if let Some(window) = app.get_webview_window(&label) {
// Janela ja existe - apenas mostrar (NAO minimizar se estiver expandida)
// Isso permite que o usuario mantenha o chat aberto enquanto recebe mensagens
let _ = window.show();
// Verificar se esta expandida (altura > 100px significa expandido)
// Se estiver expandida, NAO minimizar - usuario esta usando o chat
if let Ok(size) = window.inner_size() {
let is_expanded = size.height > 100;
if !is_expanded {
// Janela esta minimizada, manter minimizada
let _ = set_chat_minimized(app, &session.ticket_id, true);
}
// Se esta expandida, nao faz nada - deixa o usuario continuar usando
}
} else {
// Criar nova janela ja minimizada (menos intrusivo)
let _ = open_chat_window(app, &session.ticket_id, session.ticket_ref);
}
}
}
@ -1201,3 +1222,85 @@ pub fn set_chat_minimized(app: &tauri::AppHandle, ticket_id: &str, minimized: bo
crate::log_info!("Chat {} -> minimized={}", ticket_id, minimized);
Ok(())
}
// ============================================================================
// HUB WINDOW MANAGEMENT (Lista de todas as sessoes)
// ============================================================================
const HUB_WINDOW_LABEL: &str = "chat-hub";
pub fn open_hub_window(app: &tauri::AppHandle) -> Result<(), String> {
open_hub_window_with_state(app, true) // Por padrao abre minimizada
}
fn open_hub_window_with_state(app: &tauri::AppHandle, start_minimized: bool) -> Result<(), String> {
// Verificar se ja existe
if let Some(window) = app.get_webview_window(HUB_WINDOW_LABEL) {
window.show().map_err(|e| e.to_string())?;
window.set_focus().map_err(|e| e.to_string())?;
return Ok(());
}
// Dimensoes baseadas no estado inicial
let (width, height) = if start_minimized {
(200.0, 52.0) // Tamanho minimizado (chip)
} else {
(380.0, 480.0) // Tamanho expandido (lista)
};
// Posicionar no canto inferior direito
let (x, y) = resolve_chat_window_position(app, None, width, height);
// URL para modo hub
let url_path = "index.html?view=chat&hub=true";
WebviewWindowBuilder::new(
app,
HUB_WINDOW_LABEL,
WebviewUrl::App(url_path.into()),
)
.title("Chats de Suporte")
.inner_size(width, height)
.min_inner_size(200.0, 52.0)
.position(x, y)
.decorations(false)
.transparent(true)
.shadow(false)
.always_on_top(true)
.skip_taskbar(true)
.focused(true)
.visible(true)
.build()
.map_err(|e| e.to_string())?;
// Reaplica layout/posicao
let _ = set_hub_minimized(app, start_minimized);
crate::log_info!("Hub window aberta (minimizada={})", start_minimized);
Ok(())
}
pub fn close_hub_window(app: &tauri::AppHandle) -> Result<(), String> {
if let Some(window) = app.get_webview_window(HUB_WINDOW_LABEL) {
window.close().map_err(|e| e.to_string())?;
}
Ok(())
}
pub fn set_hub_minimized(app: &tauri::AppHandle, minimized: bool) -> Result<(), String> {
let window = app.get_webview_window(HUB_WINDOW_LABEL).ok_or("Hub window não encontrada")?;
let (width, height) = if minimized {
(200.0, 52.0) // Chip minimizado
} else {
(380.0, 480.0) // Lista expandida
};
let (x, y) = resolve_chat_window_position(app, Some(&window), width, height);
window.set_size(tauri::LogicalSize::new(width, height)).map_err(|e| e.to_string())?;
window.set_position(tauri::LogicalPosition::new(x, y)).map_err(|e| e.to_string())?;
crate::log_info!("Hub -> minimized={}", minimized);
Ok(())
}