fix: corrige multiplos problemas de chat e infra
- stack.yml: reduz replicas web para 1 (SQLite nao suporta escrita concorrente) - chat.rs: janela de chat ja abre minimizada para evitar marcar mensagens como lidas prematuramente - rustdesk.rs: preserva ID existente do RustDesk ao reprovisionar (evita criar novo ID a cada reinstalacao do Raven) - ChatWidget.tsx: remove isMinimized das dependencias do useEffect para evitar memory leak de resubscriptions 🤖 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
695a44781a
commit
fb97d9bec8
4 changed files with 80 additions and 34 deletions
|
|
@ -675,14 +675,16 @@ async fn process_chat_update(
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mostrar janela de chat minimizada (menos intrusivo que abrir completo)
|
// Mostrar janela de chat minimizada (menos intrusivo que abrir completo)
|
||||||
|
// A janela ja abre minimizada por padrao (start_minimized=true)
|
||||||
if let Some(session) = current_sessions.first() {
|
if let Some(session) = current_sessions.first() {
|
||||||
let label = format!("chat-{}", session.ticket_id);
|
let label = format!("chat-{}", session.ticket_id);
|
||||||
if let Some(window) = app.get_webview_window(&label) {
|
if let Some(window) = app.get_webview_window(&label) {
|
||||||
|
// Janela ja existe - apenas mostrar e garantir que esta minimizada
|
||||||
let _ = window.show();
|
let _ = window.show();
|
||||||
let _ = set_chat_minimized(app, &session.ticket_id, true);
|
let _ = set_chat_minimized(app, &session.ticket_id, true);
|
||||||
} else {
|
} else {
|
||||||
|
// Criar nova janela ja minimizada (sem necessidade de chamar set_chat_minimized depois)
|
||||||
let _ = open_chat_window(app, &session.ticket_id, session.ticket_ref);
|
let _ = open_chat_window(app, &session.ticket_id, session.ticket_ref);
|
||||||
let _ = set_chat_minimized(app, &session.ticket_id, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -707,6 +709,11 @@ async fn process_chat_update(
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64) -> Result<(), String> {
|
fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64) -> Result<(), String> {
|
||||||
|
open_chat_window_with_state(app, ticket_id, ticket_ref, true) // Por padrao abre minimizada
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Abre janela de chat com estado inicial de minimizacao configuravel
|
||||||
|
fn open_chat_window_with_state(app: &tauri::AppHandle, ticket_id: &str, ticket_ref: u64, start_minimized: bool) -> Result<(), String> {
|
||||||
let label = format!("chat-{}", ticket_id);
|
let label = format!("chat-{}", ticket_id);
|
||||||
|
|
||||||
// Verificar se ja existe
|
// Verificar se ja existe
|
||||||
|
|
@ -716,6 +723,13 @@ fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dimensoes baseadas no estado inicial
|
||||||
|
let (width, height) = if start_minimized {
|
||||||
|
(240.0, 52.0) // Tamanho minimizado (chip com badge)
|
||||||
|
} else {
|
||||||
|
(380.0, 520.0) // Tamanho expandido
|
||||||
|
};
|
||||||
|
|
||||||
// Obter tamanho da tela para posicionar no canto inferior direito
|
// Obter tamanho da tela para posicionar no canto inferior direito
|
||||||
let monitors = app.available_monitors().map_err(|e| e.to_string())?;
|
let monitors = app.available_monitors().map_err(|e| e.to_string())?;
|
||||||
let primary = monitors.into_iter().next();
|
let primary = monitors.into_iter().next();
|
||||||
|
|
@ -723,8 +737,6 @@ fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref
|
||||||
let (x, y) = if let Some(monitor) = primary {
|
let (x, y) = if let Some(monitor) = primary {
|
||||||
let size = monitor.size();
|
let size = monitor.size();
|
||||||
let scale = monitor.scale_factor();
|
let scale = monitor.scale_factor();
|
||||||
let width = 380.0;
|
|
||||||
let height = 520.0;
|
|
||||||
let margin = 20.0;
|
let margin = 20.0;
|
||||||
let taskbar_height = 50.0;
|
let taskbar_height = 50.0;
|
||||||
(
|
(
|
||||||
|
|
@ -744,7 +756,7 @@ fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref
|
||||||
WebviewUrl::App(url_path.into()),
|
WebviewUrl::App(url_path.into()),
|
||||||
)
|
)
|
||||||
.title("Chat de Suporte")
|
.title("Chat de Suporte")
|
||||||
.inner_size(380.0, 520.0)
|
.inner_size(width, height) // Abre ja no tamanho correto
|
||||||
.min_inner_size(240.0, 52.0) // Tamanho minimo para modo minimizado com badge
|
.min_inner_size(240.0, 52.0) // Tamanho minimo para modo minimizado com badge
|
||||||
.position(x, y)
|
.position(x, y)
|
||||||
.decorations(false) // Sem decoracoes nativas - usa header customizado
|
.decorations(false) // Sem decoracoes nativas - usa header customizado
|
||||||
|
|
@ -757,7 +769,7 @@ fn open_chat_window_internal(app: &tauri::AppHandle, ticket_id: &str, ticket_ref
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
crate::log_info!("Janela de chat aberta: {}", label);
|
crate::log_info!("Janela de chat aberta (minimizada={}): {}", start_minimized, label);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,27 @@ struct ReleaseResponse {
|
||||||
assets: Vec<ReleaseAsset>,
|
assets: Vec<ReleaseAsset>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Auxiliar para definir ID customizado baseado no machine_id
|
||||||
|
fn define_custom_id_from_machine(exe_path: &Path, machine_id: Option<&str>) -> Option<String> {
|
||||||
|
if let Some(value) = machine_id.and_then(|raw| {
|
||||||
|
let trimmed = raw.trim();
|
||||||
|
if trimmed.is_empty() { None } else { Some(trimmed) }
|
||||||
|
}) {
|
||||||
|
match set_custom_id(exe_path, value) {
|
||||||
|
Ok(custom) => {
|
||||||
|
log_event(&format!("ID determinístico definido: {custom}"));
|
||||||
|
Some(custom)
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
log_event(&format!("Falha ao definir ID determinístico: {error}"));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ensure_rustdesk(
|
pub fn ensure_rustdesk(
|
||||||
config_string: Option<&str>,
|
config_string: Option<&str>,
|
||||||
password_override: Option<&str>,
|
password_override: Option<&str>,
|
||||||
|
|
@ -91,6 +112,13 @@ pub fn ensure_rustdesk(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IMPORTANTE: Ler o ID existente ANTES de qualquer limpeza
|
||||||
|
// Isso preserva o ID quando o Raven é reinstalado mas o RustDesk permanece
|
||||||
|
let preserved_remote_id = read_remote_id_from_profiles();
|
||||||
|
if let Some(ref id) = preserved_remote_id {
|
||||||
|
log_event(&format!("ID existente preservado antes da limpeza: {}", id));
|
||||||
|
}
|
||||||
|
|
||||||
let exe_path = detect_executable_path();
|
let exe_path = detect_executable_path();
|
||||||
let (installed_version, freshly_installed) = ensure_installed(&exe_path)?;
|
let (installed_version, freshly_installed) = ensure_installed(&exe_path)?;
|
||||||
log_event(if freshly_installed {
|
log_event(if freshly_installed {
|
||||||
|
|
@ -106,12 +134,18 @@ pub fn ensure_rustdesk(
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// So limpa perfis se for instalacao fresca (RustDesk nao existia)
|
||||||
|
// Se ja existia, preservamos o ID para manter consistencia
|
||||||
|
if freshly_installed {
|
||||||
match purge_existing_rustdesk_profiles() {
|
match purge_existing_rustdesk_profiles() {
|
||||||
Ok(_) => log_event("Configurações antigas do RustDesk limpas antes da reaplicação"),
|
Ok(_) => log_event("Configurações antigas do RustDesk limpas (instalação fresca)"),
|
||||||
Err(error) => log_event(&format!(
|
Err(error) => log_event(&format!(
|
||||||
"Aviso: não foi possível limpar completamente os perfis existentes do RustDesk ({error})"
|
"Aviso: não foi possível limpar completamente os perfis existentes do RustDesk ({error})"
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log_event("Mantendo perfis existentes do RustDesk (preservando ID)");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(value) = config_string.and_then(|raw| {
|
if let Some(value) = config_string.and_then(|raw| {
|
||||||
let trimmed = raw.trim();
|
let trimmed = raw.trim();
|
||||||
|
|
@ -169,22 +203,19 @@ pub fn ensure_rustdesk(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let custom_id = if let Some(value) = machine_id.and_then(|raw| {
|
// Se ja existe um ID preservado E o RustDesk nao foi recem-instalado, usa o ID existente
|
||||||
let trimmed = raw.trim();
|
// Isso garante que reinstalar o Raven nao muda o ID do RustDesk
|
||||||
if trimmed.is_empty() { None } else { Some(trimmed) }
|
let custom_id = if let Some(ref existing_id) = preserved_remote_id {
|
||||||
}) {
|
if !freshly_installed {
|
||||||
match set_custom_id(&exe_path, value) {
|
log_event(&format!("Reutilizando ID existente do RustDesk: {}", existing_id));
|
||||||
Ok(custom) => {
|
Some(existing_id.clone())
|
||||||
log_event(&format!("ID determinístico definido: {custom}"));
|
} else {
|
||||||
Some(custom)
|
// Instalacao fresca - define novo ID baseado no machine_id
|
||||||
}
|
define_custom_id_from_machine(&exe_path, machine_id)
|
||||||
Err(error) => {
|
|
||||||
log_event(&format!("Falha ao definir ID determinístico: {error}"));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
// Sem ID preservado - define novo ID baseado no machine_id
|
||||||
|
define_custom_id_from_machine(&exe_path, machine_id)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(error) = ensure_service_running(&exe_path) {
|
if let Err(error) = ensure_service_running(&exe_path) {
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,13 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
|
||||||
hadSessionRef.current = hasSession
|
hadSessionRef.current = hasSession
|
||||||
}, [hasSession, ticketId])
|
}, [hasSession, ticketId])
|
||||||
|
|
||||||
// Inicializacao via Convex (WS)
|
// Ref para acessar isMinimized dentro do callback sem causar resubscription
|
||||||
|
const isMinimizedRef = useRef(isMinimized)
|
||||||
|
useEffect(() => {
|
||||||
|
isMinimizedRef.current = isMinimized
|
||||||
|
}, [isMinimized])
|
||||||
|
|
||||||
|
// Inicializacao via Convex (WS) - NAO depende de isMinimized para evitar resubscriptions
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setMessages([])
|
setMessages([])
|
||||||
|
|
@ -107,13 +113,8 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
|
||||||
const first = payload.messages[0]
|
const first = payload.messages[0]
|
||||||
setTicketInfo((prevInfo) => prevInfo ?? { ref: 0, subject: "", agentName: first.authorName ?? "Suporte" })
|
setTicketInfo((prevInfo) => prevInfo ?? { ref: 0, subject: "", agentName: first.authorName ?? "Suporte" })
|
||||||
}
|
}
|
||||||
// Marca como lidas se a janela estiver expandida e houver nao lidas
|
// NAO marca como lidas aqui - deixa o useEffect de expansao fazer isso
|
||||||
if (backendUnreadCount > 0 && !isMinimized) {
|
// Isso evita marcar como lidas antes do usuario expandir o chat
|
||||||
const unreadIds = payload.messages.filter(m => !m.isFromMachine).map(m => m.id as string)
|
|
||||||
if (unreadIds.length > 0) {
|
|
||||||
markMachineMessagesRead(ticketId, unreadIds).catch(err => console.error("mark read falhou", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|
@ -127,7 +128,7 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
|
||||||
messagesSubRef.current?.()
|
messagesSubRef.current?.()
|
||||||
messagesSubRef.current = null
|
messagesSubRef.current = null
|
||||||
}
|
}
|
||||||
}, [ticketId, isMinimized])
|
}, [ticketId]) // Removido isMinimized - evita memory leak de resubscriptions
|
||||||
|
|
||||||
// Sincroniza estado de minimizado com o tamanho da janela (Tauri pode alterar por fora)
|
// Sincroniza estado de minimizado com o tamanho da janela (Tauri pode alterar por fora)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,9 @@ services:
|
||||||
RELEASE_SHA: "${RELEASE_SHA:-dev}"
|
RELEASE_SHA: "${RELEASE_SHA:-dev}"
|
||||||
deploy:
|
deploy:
|
||||||
mode: replicated
|
mode: replicated
|
||||||
replicas: 2
|
# IMPORTANTE: SQLite nao suporta multiplas conexoes de escrita simultaneas.
|
||||||
|
# Manter sempre 1 replica para evitar "attempt to write a readonly database".
|
||||||
|
replicas: 1
|
||||||
update_config:
|
update_config:
|
||||||
parallelism: 1
|
parallelism: 1
|
||||||
# start-first evita downtime: sobe o novo task antes de parar o anterior
|
# start-first evita downtime: sobe o novo task antes de parar o anterior
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue