Files
whatsappNucleo/app/components/debug/HistorySection.vue
josedario87 c0af0a3478
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s
Feature: Cargar historial de WhatsApp desde la UI
- 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
2025-12-02 21:59:27 -06:00

231 lines
6.7 KiB
Vue

<template>
<div class="space-y-6 p-4">
<!-- Fetch Message History -->
<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 de WhatsApp. Los mensajes se guardaran en la base de datos.
</p>
<!-- 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"
class="w-48"
/>
</div>
<!-- Action Button -->
<UButton
:loading="loading"
:disabled="!instanceId || !count"
@click="fetchHistory"
color="primary"
>
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
}>()
const emit = defineEmits<{
(e: 'response', data: any): void
}>()
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
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', {
method: 'POST',
body: {
instanceId: props.instanceId,
count: count.value,
oldestMsgKey,
oldestMsgTimestamp
}
})
emit('response', result)
} catch (error: any) {
emit('response', { success: false, error: error.data?.message || error.message })
} finally {
loading.value = false
}
}
</script>