se incluye al propio agente usuario de la cuenta de whatsapp dentro del convo, se actualiza system prompt para el agent
All checks were successful
Deploy conversation layer / deploy (push) Successful in 22s

This commit is contained in:
2025-06-05 15:24:18 -06:00
parent b66b037f1f
commit fff921df0a
2 changed files with 163 additions and 118 deletions

View File

@@ -1,145 +1,131 @@
import axios from 'axios';
import { WhatsAppMessage, Conversation, Msg, Participant } from '../types';
/**
* Inmemory cache of conversations indexed by chatId.
*/
const conversations = new Map<string, Conversation>();
async function loadMessages(
chatId: string,
openWaUrl: string
): Promise<WhatsAppMessage[]> {
console.log(`[conversationStore] Loading messages for ${chatId}`);
// ─────────────────────────────────────────────────────── internal helpers ────
/**
* Fetches *all* messages for a chat using OpenWA and returns them asis.
*/
async function fetchChatMessages(chatId: string, openWaUrl: string): Promise<WhatsAppMessage[]> {
const { data } = await axios.post(`${openWaUrl}/loadAndGetAllMessagesInChat`, {
args: {
chatId,
includeMe: true,
includeNotifications: true,
},
args: { chatId, includeMe: true, includeNotifications: true },
});
const msgs: WhatsAppMessage[] = data?.response || data || [];
return msgs;
return data?.response ?? data ?? [];
}
function mapMessage(m: WhatsAppMessage): Msg {
/**
* Builds a complete {@link Conversation} object from raw WA messages.
*/
export async function buildConversation(chatId: string, openWaUrl: string): Promise<Conversation> {
const raw = await fetchChatMessages(chatId, openWaUrl);
const chatMeta = raw[0]?.chat;
const convo: Conversation = {
chatId,
title: chatMeta?.formattedTitle ?? chatMeta?.name ?? chatId,
isGroup: Boolean(chatMeta?.isGroup),
unreadCount: chatMeta?.unreadCount ?? 0,
participants: buildParticipants(raw),
messages: raw.slice(-100).map(toMsg).sort((a, b) => a.ts - b.ts),
createdAt: Date.now(),
};
conversations.set(chatId, convo);
return convo;
}
/**
* Collects unique participants from every message (and group metadata if any).
*/
function buildParticipants(messages: WhatsAppMessage[]): Participant[] {
const map = new Map<string, Participant>();
for (const m of messages) {
const s: any = m.sender ?? m.chat?.contact ?? null;
if (!s) continue;
if (!map.has(s.id)) {
map.set(s.id, {
id: s.id,
name: s.pushname || s.name || '',
isMe: Boolean(s.isMe),
isAdmin: Boolean(s.isAdmin || s.isSuperAdmin),
});
}
}
return [...map.values()];
}
function ensureParticipant(list: Participant[], sender: any): void {
if (!sender) return;
if (list.some((p) => p.id === sender.id)) return;
list.push({
id: sender.id,
name: sender.pushname || sender.name || '',
isMe: Boolean(sender.isMe),
});
}
/**
* Normalises a raw WhatsApp message to the lightweight {@link Msg} format.
*/
function toMsg(m: WhatsAppMessage): Msg {
const anyMsg = m as any;
return {
id: m.id,
from: m.from,
to: m.to,
ts: (m as any).timestamp || (m as any).t,
type: ((m as any).type as any) || 'chat',
text: (m as any).text || (m as any).caption || (m as any).body,
mediaUrl: (m as any).cloudUrl || (m as any).clientUrl,
mentions: ((m as any).mentionedJidList as any) || [],
ts: anyMsg.timestamp ?? anyMsg.t ?? Date.now(),
type: anyMsg.type ?? 'chat',
text: anyMsg.text ?? anyMsg.caption ?? anyMsg.body ?? '',
mediaUrl: anyMsg.cloudUrl ?? anyMsg.clientUrl ?? undefined,
mentions: anyMsg.mentionedJidList ?? [],
meta: {
ack: (m as any).ack || 0,
hasReaction: (m as any).hasReaction || false,
isQuoted: !!(m as any).quotedMsg,
ack: anyMsg.ack ?? 0,
hasReaction: Boolean(anyMsg.hasReaction),
isQuoted: Boolean(anyMsg.quotedMsg),
},
};
}
export async function getConversation(
chatId: string,
openWaUrl: string
): Promise<Conversation> {
console.log(`[conversationStore] Retrieving conversation for ${chatId}`);
// ─────────────────────────────────────────────────────────── public API ──────
export async function getConversation(chatId: string, openWaUrl: string): Promise<Conversation> {
let conv = conversations.get(chatId);
if (!conv) {
conv = await buildConversation(chatId, openWaUrl);
}
if (!conv) conv = await buildConversation(chatId, openWaUrl);
return conv;
}
export function listConversations(): Conversation[] {
console.log('[conversationStore] Listing conversations');
return Array.from(conversations.values());
}
export async function buildConversation(
chatId: string,
openWaUrl: string
): Promise<Conversation> {
console.log(`[conversationStore] Building conversation for ${chatId}`);
const rawMessages = await loadMessages(chatId, openWaUrl);
const now = Date.now();
const first = rawMessages[0];
const chat = first?.chat;
const title = chat?.formattedTitle || chat?.name || chatId;
const isGroup = chat?.isGroup || false;
const unreadCount = chat?.unreadCount || 0;
const participantsMap = new Map<string, Participant>();
if (chat?.contact) {
const c = chat.contact;
participantsMap.set(c.id, {
id: c.id,
name: c.pushname || c.name || '',
isMe: c.isMe,
});
}
if (isGroup && chat?.groupMetadata?.participants) {
for (const p of chat.groupMetadata.participants as any[]) {
const c = p.contact || {};
const id = c.id || p.id;
participantsMap.set(id, {
id,
name: c.pushname || c.name || '',
isMe: c.isMe || false,
isAdmin: p.isAdmin || p.isSuperAdmin,
});
}
}
for (const m of rawMessages) {
const s = m.sender;
if (s && !participantsMap.has(s.id)) {
participantsMap.set(s.id, {
id: s.id,
name: s.pushname || s.name || '',
isMe: s.isMe,
});
}
}
const messages: Msg[] = rawMessages.slice(-20).map(mapMessage);
messages.sort((a, b) => a.ts - b.ts);
const conv: Conversation = {
chatId,
title,
isGroup,
unreadCount,
participants: Array.from(participantsMap.values()),
messages,
createdAt: conversations.get(chatId)?.createdAt || now,
};
conversations.set(chatId, conv);
return conv;
return [...conversations.values()];
}
export function deleteConversation(chatId: string): boolean {
console.log(`[conversationStore] Deleting conversation ${chatId}`);
return conversations.delete(chatId);
}
export async function addMessageToConversation(
chatId: string,
msg: WhatsAppMessage,
openWaUrl: string
waMsg: WhatsAppMessage,
openWaUrl: string,
): Promise<Conversation> {
console.log(`[conversationStore] Adding message to ${chatId}`);
const conv = await getConversation(chatId, openWaUrl);
const mapped = mapMessage(msg);
conv.messages.push(mapped);
if (conv.messages.length > 20) conv.messages.shift();
const s = msg.sender;
if (s && !conv.participants.some((p) => p.id === s.id)) {
conv.participants.push({
id: s.id,
name: s.pushname || s.name || '',
isMe: s.isMe,
});
}
return conv;
}
const convo = await getConversation(chatId, openWaUrl);
const msg = toMsg(waMsg);
convo.messages.push(msg);
// keep last 100 msgs only
if (convo.messages.length > 100) convo.messages.shift();
ensureParticipant(convo.participants, waMsg.sender);
return convo;
}