|
|
@@ -6,6 +6,14 @@ let singleton;
|
|
|
|
|
|
const POLL_INTERVAL_MS = 3000;
|
|
|
|
|
|
+function compararMensagens(a, b) {
|
|
|
+ return new Date(a.Timestamp) - new Date(b.Timestamp) || a.Id - b.Id;
|
|
|
+}
|
|
|
+
|
|
|
+function maiorId(mensagens, atual) {
|
|
|
+ return mensagens.reduce((max, m) => (max == null || m.Id > max ? m.Id : max), atual);
|
|
|
+}
|
|
|
+
|
|
|
export function resetConversasSingleton() {
|
|
|
if (singleton) {
|
|
|
singleton.fecharConversa();
|
|
|
@@ -33,7 +41,8 @@ export function useConversas() {
|
|
|
const sugestaoError = ref("");
|
|
|
|
|
|
let pollTimer = null;
|
|
|
- let ultimoTimestamp = null;
|
|
|
+ // cursor do polling: maior Id já recebido do servidor (ver listarMensagens no backend)
|
|
|
+ let ultimoId = null;
|
|
|
let buscandoNovasMensagens = false;
|
|
|
let conversasEpoch = 0;
|
|
|
// duplo clique na mesma conversa passa pelo guard de id; o epoch distingue as aberturas
|
|
|
@@ -121,13 +130,14 @@ export function useConversas() {
|
|
|
const idConsultado = conversaAtivaId.value;
|
|
|
buscandoNovasMensagens = true;
|
|
|
try {
|
|
|
- const { results } = await listarMensagensConversa(idConsultado, ultimoTimestamp);
|
|
|
+ const { results } = await listarMensagensConversa(idConsultado, ultimoId);
|
|
|
if (conversaAtivaId.value !== idConsultado) return;
|
|
|
if (results.length > 0) {
|
|
|
const idsExistentes = new Set(mensagens.value.map((m) => m.Id));
|
|
|
const novas = results.filter((m) => !idsExistentes.has(m.Id));
|
|
|
- mensagens.value = [...mensagens.value, ...novas];
|
|
|
- ultimoTimestamp = results[results.length - 1].Timestamp;
|
|
|
+ // uma entrada atrasada pelo webhook pode ter Timestamp anterior às já exibidas
|
|
|
+ mensagens.value = [...mensagens.value, ...novas].sort(compararMensagens);
|
|
|
+ ultimoId = maiorId(results, ultimoId);
|
|
|
if (novas.some((m) => m.Direcao === "entrada")) buscarSugestao();
|
|
|
}
|
|
|
} catch {
|
|
|
@@ -150,7 +160,7 @@ export function useConversas() {
|
|
|
conversaAtivaId.value = id;
|
|
|
mensagens.value = [];
|
|
|
limparMidiaCache();
|
|
|
- ultimoTimestamp = null;
|
|
|
+ ultimoId = null;
|
|
|
invalidarSugestao();
|
|
|
mensagensLoading.value = true;
|
|
|
mensagensError.value = "";
|
|
|
@@ -159,7 +169,7 @@ export function useConversas() {
|
|
|
const { results } = await listarMensagensConversa(id);
|
|
|
if (abertura !== aberturaEpoch) return;
|
|
|
mensagens.value = results;
|
|
|
- ultimoTimestamp = results.length > 0 ? results[results.length - 1].Timestamp : null;
|
|
|
+ ultimoId = maiorId(results, null);
|
|
|
if (results.length && results[results.length - 1].Direcao === "entrada") buscarSugestao();
|
|
|
} catch (err) {
|
|
|
if (abertura !== aberturaEpoch) return;
|
|
|
@@ -184,7 +194,7 @@ export function useConversas() {
|
|
|
mensagens.value = [];
|
|
|
mensagensLoading.value = false;
|
|
|
limparMidiaCache();
|
|
|
- ultimoTimestamp = null;
|
|
|
+ ultimoId = null;
|
|
|
invalidarSugestao();
|
|
|
}
|
|
|
|
|
|
@@ -195,8 +205,9 @@ export function useConversas() {
|
|
|
try {
|
|
|
const mensagem = await responderConversa(idAlvo, texto.trim());
|
|
|
if (conversaAtivaId.value !== idAlvo) return;
|
|
|
+ // não avança o cursor: uma entrada gravada antes desta resposta (Id menor) pode ainda
|
|
|
+ // não ter vindo no polling; o próximo ciclo a traz e a dedup por Id ignora esta
|
|
|
mensagens.value.push(mensagem);
|
|
|
- ultimoTimestamp = mensagem.Timestamp;
|
|
|
sugestao.value = null;
|
|
|
sugestaoError.value = "";
|
|
|
await carregarConversas();
|