Feature: Cargar historial de WhatsApp desde la UI
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
- Agregar endpoint oldest.get.ts para obtener mensaje mas antiguo de un chat - Agregar boton 'Cargar historial de WhatsApp' en vista de mensajes - Mejorar HistorySection.vue con selector de chats y auto-deteccion
This commit is contained in:
@@ -4,43 +4,105 @@
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-medium text-[var(--wa-text)]">Solicitar Historial de Mensajes</h3>
|
||||
<p class="text-sm text-[var(--wa-text-muted)]">
|
||||
Solicita mensajes adicionales del historial. Los resultados llegaran via el evento messaging-history.set.
|
||||
Solicita mensajes adicionales del historial de WhatsApp. Los mensajes se guardaran en la base de datos.
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
|
||||
<!-- Chat Selector -->
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm text-[var(--wa-text-muted)]">Seleccionar Chat</label>
|
||||
<USelectMenu
|
||||
v-model="selectedChat"
|
||||
:items="chatOptions"
|
||||
placeholder="Seleccionar chat..."
|
||||
searchable
|
||||
:loading="loadingChats"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Selected Chat Info -->
|
||||
<div v-if="selectedChat && oldestMessage" class="p-3 rounded bg-[var(--wa-bg-hover)] space-y-1">
|
||||
<p class="text-sm text-[var(--wa-text)]">
|
||||
<span class="text-[var(--wa-text-muted)]">Chat:</span> {{ selectedChat.label }}
|
||||
</p>
|
||||
<p class="text-sm text-[var(--wa-text)]">
|
||||
<span class="text-[var(--wa-text-muted)]">Mensaje mas antiguo:</span>
|
||||
{{ formatDate(oldestMessage.timestamp) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="selectedChat && !loadingOldest" class="p-3 rounded bg-yellow-900/20 text-yellow-400 text-sm">
|
||||
Este chat no tiene mensajes guardados en la base de datos.
|
||||
</div>
|
||||
|
||||
<!-- Count Input -->
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm text-[var(--wa-text-muted)]">Cantidad de mensajes a solicitar</label>
|
||||
<UInput
|
||||
v-model.number="count"
|
||||
type="number"
|
||||
placeholder="Cantidad de mensajes"
|
||||
:min="1"
|
||||
:max="1000"
|
||||
/>
|
||||
<UInput
|
||||
v-model.number="oldestMsgTimestamp"
|
||||
type="number"
|
||||
placeholder="Timestamp mas antiguo (opcional)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-[var(--wa-text-muted)]">Oldest Message Key (opcional, JSON):</p>
|
||||
<UTextarea
|
||||
v-model="oldestMsgKeyJson"
|
||||
placeholder='{"remoteJid": "...", "id": "...", "fromMe": false}'
|
||||
:rows="3"
|
||||
class="font-mono text-sm"
|
||||
class="w-48"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<UButton
|
||||
:loading="loading"
|
||||
:disabled="!instanceId || !count"
|
||||
@click="fetchHistory"
|
||||
color="primary"
|
||||
>
|
||||
Solicitar Historial
|
||||
Solicitar {{ count }} mensajes del historial
|
||||
</UButton>
|
||||
|
||||
<!-- Advanced Mode Toggle -->
|
||||
<div class="pt-4 border-t border-[var(--wa-border)]">
|
||||
<UCheckbox v-model="showAdvanced" label="Modo avanzado (JSON manual)" />
|
||||
</div>
|
||||
|
||||
<!-- Advanced Mode Fields -->
|
||||
<div v-if="showAdvanced" class="space-y-4 p-4 rounded bg-[var(--wa-bg-hover)]">
|
||||
<p class="text-xs text-[var(--wa-text-muted)]">
|
||||
Estos campos permiten especificar manualmente el mensaje de referencia para la sincronizacion.
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<UInput
|
||||
v-model.number="manualTimestamp"
|
||||
type="number"
|
||||
placeholder="Timestamp (segundos)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm text-[var(--wa-text-muted)]">Message Key (JSON):</label>
|
||||
<UTextarea
|
||||
v-model="oldestMsgKeyJson"
|
||||
placeholder='{"remoteJid": "...", "id": "...", "fromMe": false}'
|
||||
:rows="3"
|
||||
class="font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Chat {
|
||||
id: string
|
||||
jid: string
|
||||
name: string
|
||||
isGroup: boolean
|
||||
}
|
||||
|
||||
interface OldestMessage {
|
||||
hasMessages: boolean
|
||||
messageKey: { remoteJid: string; id: string; fromMe: boolean } | null
|
||||
timestamp: number | null
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
instanceId: string | null
|
||||
}>()
|
||||
@@ -49,23 +111,104 @@ const emit = defineEmits<{
|
||||
(e: 'response', data: any): void
|
||||
}>()
|
||||
|
||||
const count = ref<number>(50)
|
||||
const oldestMsgTimestamp = ref<number | null>(null)
|
||||
const oldestMsgKeyJson = ref('')
|
||||
const count = ref<number>(100)
|
||||
const loading = ref(false)
|
||||
const showAdvanced = ref(false)
|
||||
const oldestMsgKeyJson = ref('')
|
||||
const manualTimestamp = ref<number | null>(null)
|
||||
|
||||
// Chat selection
|
||||
const chats = ref<Chat[]>([])
|
||||
const selectedChat = ref<{ label: string; value: string; jid: string } | null>(null)
|
||||
const loadingChats = ref(false)
|
||||
const oldestMessage = ref<OldestMessage | null>(null)
|
||||
const loadingOldest = ref(false)
|
||||
|
||||
// Computed chat options for select menu
|
||||
const chatOptions = computed(() =>
|
||||
chats.value.map(chat => ({
|
||||
label: chat.name || chat.jid.split('@')[0],
|
||||
value: chat.id,
|
||||
jid: chat.jid
|
||||
}))
|
||||
)
|
||||
|
||||
// Load chats when instanceId changes
|
||||
watch(() => props.instanceId, async (instanceId) => {
|
||||
if (!instanceId) {
|
||||
chats.value = []
|
||||
selectedChat.value = null
|
||||
return
|
||||
}
|
||||
|
||||
loadingChats.value = true
|
||||
try {
|
||||
chats.value = await $fetch<Chat[]>(`/api/messages/${instanceId}/chats`)
|
||||
} catch (e) {
|
||||
console.error('Error loading chats:', e)
|
||||
chats.value = []
|
||||
} finally {
|
||||
loadingChats.value = false
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// Load oldest message when chat is selected
|
||||
watch(selectedChat, async (chat) => {
|
||||
if (!chat || !props.instanceId) {
|
||||
oldestMessage.value = null
|
||||
return
|
||||
}
|
||||
|
||||
loadingOldest.value = true
|
||||
try {
|
||||
oldestMessage.value = await $fetch<OldestMessage>(
|
||||
`/api/messages/${props.instanceId}/${chat.value}/oldest`
|
||||
)
|
||||
|
||||
// Auto-populate JSON field if not in advanced mode
|
||||
if (oldestMessage.value?.hasMessages && oldestMessage.value.messageKey) {
|
||||
oldestMsgKeyJson.value = JSON.stringify(oldestMessage.value.messageKey, null, 2)
|
||||
manualTimestamp.value = oldestMessage.value.timestamp
|
||||
} else {
|
||||
oldestMsgKeyJson.value = ''
|
||||
manualTimestamp.value = null
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading oldest message:', e)
|
||||
oldestMessage.value = null
|
||||
} finally {
|
||||
loadingOldest.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// Format timestamp to readable date
|
||||
const formatDate = (timestamp: number | null) => {
|
||||
if (!timestamp) return 'N/A'
|
||||
return new Date(timestamp * 1000).toLocaleString()
|
||||
}
|
||||
|
||||
const fetchHistory = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
let oldestMsgKey = undefined
|
||||
if (oldestMsgKeyJson.value.trim()) {
|
||||
try {
|
||||
oldestMsgKey = JSON.parse(oldestMsgKeyJson.value)
|
||||
} catch {
|
||||
emit('response', { success: false, error: 'Invalid JSON for oldestMsgKey' })
|
||||
loading.value = false
|
||||
return
|
||||
let oldestMsgTimestamp = undefined
|
||||
|
||||
if (showAdvanced.value) {
|
||||
// Use manual JSON input
|
||||
if (oldestMsgKeyJson.value.trim()) {
|
||||
try {
|
||||
oldestMsgKey = JSON.parse(oldestMsgKeyJson.value)
|
||||
} catch {
|
||||
emit('response', { success: false, error: 'Invalid JSON for oldestMsgKey' })
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
oldestMsgTimestamp = manualTimestamp.value || undefined
|
||||
} else if (oldestMessage.value?.hasMessages) {
|
||||
// Use auto-detected oldest message
|
||||
oldestMsgKey = oldestMessage.value.messageKey
|
||||
oldestMsgTimestamp = oldestMessage.value.timestamp
|
||||
}
|
||||
|
||||
const result = await $fetch('/api/debug/history/fetch', {
|
||||
@@ -74,7 +217,7 @@ const fetchHistory = async () => {
|
||||
instanceId: props.instanceId,
|
||||
count: count.value,
|
||||
oldestMsgKey,
|
||||
oldestMsgTimestamp: oldestMsgTimestamp.value || undefined
|
||||
oldestMsgTimestamp
|
||||
}
|
||||
})
|
||||
emit('response', result)
|
||||
|
||||
@@ -188,6 +188,32 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Load from WhatsApp when local DB is exhausted -->
|
||||
<div
|
||||
v-else-if="!hasMoreMessages && !loadingWhatsAppHistory"
|
||||
class="flex flex-col items-center py-3 gap-2"
|
||||
>
|
||||
<span class="text-xs text-[var(--wa-text-muted)]">
|
||||
No hay mas mensajes en la base de datos
|
||||
</span>
|
||||
<button
|
||||
class="text-xs px-3 py-1.5 rounded bg-[var(--wa-green)] text-white hover:opacity-90 transition-opacity"
|
||||
@click="fetchWhatsAppHistory"
|
||||
>
|
||||
Cargar historial de WhatsApp
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="loadingWhatsAppHistory"
|
||||
class="flex flex-col items-center py-3 gap-2"
|
||||
>
|
||||
<UIcon name="i-lucide-loader-2" class="w-5 h-5 animate-spin text-[var(--wa-green)]" />
|
||||
<span class="text-xs text-[var(--wa-text-muted)]">
|
||||
Solicitando historial de WhatsApp...
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<MessagesMessageBubble
|
||||
v-for="message in reversedMessages"
|
||||
:key="message.id"
|
||||
@@ -244,6 +270,7 @@ const messages = ref<any[]>([])
|
||||
const loadingChats = ref(false)
|
||||
const loadingMessages = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const loadingWhatsAppHistory = ref(false)
|
||||
const hasMoreMessages = ref(true)
|
||||
const showDebugPanel = ref(false)
|
||||
const showChatsDebug = ref(false)
|
||||
@@ -360,6 +387,56 @@ const handleScroll = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch message history from WhatsApp (when local DB is exhausted)
|
||||
const fetchWhatsAppHistory = async () => {
|
||||
if (!selectedInstance.value?.value || !selectedChat.value) return
|
||||
|
||||
loadingWhatsAppHistory.value = true
|
||||
try {
|
||||
const instanceId = selectedInstance.value.value
|
||||
const chatId = selectedChat.value.id
|
||||
|
||||
// Get the oldest message from local DB
|
||||
const oldest = await $fetch<{
|
||||
hasMessages: boolean
|
||||
messageKey: { remoteJid: string; id: string; fromMe: boolean } | null
|
||||
timestamp: number | null
|
||||
}>(`/api/messages/${instanceId}/${chatId}/oldest`)
|
||||
|
||||
// Request history from WhatsApp
|
||||
if (!oldest.hasMessages) {
|
||||
// No messages in DB, request without reference
|
||||
await $fetch('/api/debug/history/fetch', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
instanceId,
|
||||
count: 100
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Request messages older than the oldest in DB
|
||||
await $fetch('/api/debug/history/fetch', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
instanceId,
|
||||
count: 100,
|
||||
oldestMsgKey: oldest.messageKey,
|
||||
oldestMsgTimestamp: oldest.timestamp
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Wait for history sync to complete and reload
|
||||
await new Promise(r => setTimeout(r, 3000))
|
||||
await reloadMessages()
|
||||
hasMoreMessages.value = true
|
||||
} catch (e) {
|
||||
console.error('Error fetching WhatsApp history:', e)
|
||||
} finally {
|
||||
loadingWhatsAppHistory.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Computed presence text for current chat
|
||||
const currentChatPresence = computed(() => {
|
||||
if (!selectedChat.value?.jid) return null
|
||||
|
||||
Reference in New Issue
Block a user