From 7f0861ae7796ba8d6d8012c49d07a4c23660416b Mon Sep 17 00:00:00 2001 From: josedario87 Date: Wed, 11 Jun 2025 23:09:38 -0600 Subject: [PATCH] ahora tambien enruta mensajes enviados por chatUI --- whatsapp-router/src/chatHandlers.ts | 6 +- whatsapp-router/src/index.ts | 2 + whatsapp-router/src/routes/chatUI_Actions.ts | 304 ++++++++++++++++++ .../src/routes/conversationActions.ts | 1 + 4 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 whatsapp-router/src/routes/chatUI_Actions.ts diff --git a/whatsapp-router/src/chatHandlers.ts b/whatsapp-router/src/chatHandlers.ts index 2351c51..6f431f7 100644 --- a/whatsapp-router/src/chatHandlers.ts +++ b/whatsapp-router/src/chatHandlers.ts @@ -7,7 +7,11 @@ export type Handler = string | ((conv: Conversation) => Promise); export const chatHandlers: Record = { '50498554225@c.us': process.env.CONVERSATION_AGENT_URL || 'http://conversation-layer-agent:8001', - '120363401804322608@g.us' : process.env.PLANILLA_AGENT_URL ||'http://planilla-agent:8012' + '120363401804322608@g.us' : process.env.PLANILLA_AGENT_URL ||'http://planilla-agent:8012', + 'planilla-UI' : process.env.PLANILLA_AGENT_URL ||'http://planilla-agent:8012' + + //map any conversation that follow this pattern, planilla + // Add other mappings like: // '50496210031@c.us': 'http://llm-agent:8000' }; diff --git a/whatsapp-router/src/index.ts b/whatsapp-router/src/index.ts index 5e86f0b..cceb2c9 100644 --- a/whatsapp-router/src/index.ts +++ b/whatsapp-router/src/index.ts @@ -1,6 +1,7 @@ import express from 'express'; import dotenv from 'dotenv'; import { registerConversationRoutes } from './routes/conversationActions'; +import { registerChatUIRoutes } from './routes/chatUI_Actions'; import whatsappActionsRouter from './routes/whatsappActions'; import { registerLogSse } from './sse/logSse'; import cors from 'cors'; @@ -68,6 +69,7 @@ const config: WebhookConfig = { registerConversationRoutes(app, openWaUrl); registerWebhookRoutes(app, config, openWaUrl, agentUrl); +registerChatUIRoutes(app, openWaUrl); app.use('/whatsapp', whatsappActionsRouter); // New line // Register new whatsappActions routes diff --git a/whatsapp-router/src/routes/chatUI_Actions.ts b/whatsapp-router/src/routes/chatUI_Actions.ts new file mode 100644 index 0000000..5212b05 --- /dev/null +++ b/whatsapp-router/src/routes/chatUI_Actions.ts @@ -0,0 +1,304 @@ +import { Application } from 'express'; +import express from 'express'; +import { WhatsAppMessage, Conversation, Msg } from '../types'; +import { addMessageToConversation } from '../store/conversation'; +import { mapWhatsAppMessageToMsg } from '../messageProcessor'; +import { getHandler } from '../chatHandlers'; +import axios from 'axios'; + +interface UIMessage { + chatId: string; + id: string; + from: string; + to: string; + ts: number; + type: 'chat'; + text: string; + mediaUrl: string | null; + mentions: string[] | null; + meta: { + ack: number; + hasReaction: boolean; + isQuoted: boolean; + }; +} + +const conversations = new Map(); + + +export const processIncomingMessageForConversation = ( + conversation: Conversation, + message: WhatsAppMessage + ): void => { + const mappedMsg = mapWhatsAppMessageToMsg(message); + + // Avoid duplicates if multiple webhook events deliver the same message + if (!conversation.messages.some((m) => m.id === mappedMsg.id)) { + conversation.messages.push(mappedMsg); + // if (conversation.messages.length > 20) { + // conversation.messages.shift(); // Keep only the last 20 messages + // } + } + + // Add new participants if necessary + const sender = message.sender; + if (sender && !conversation.participants.some((p) => p.id === sender.id)) { + conversation.participants.push({ + id: sender.id, + name: sender.pushname || sender.name || '', + isMe: sender.isMe, + // isAdmin property is not available here, it's part of group metadata + }); + } + // The conversation object is modified directly + }; + + + + + + +export function registerChatUIRoutes(app: Application, openWaUrl: string | undefined) { + app.post('/chatUI/sendMessage', async (req: express.Request, res: express.Response) => { + console.log(`[routes] POST /chatUI/sendMessage`, 'v2.0'); + + try { + if (!req.body || !req.body.data) { + throw new Error('Invalid request format'); + } + + const uiMessage: UIMessage = req.body.data; + + if (!uiMessage.text || !uiMessage.chatId) { + throw new Error('Missing required fields: text and chatId'); + } + + // convertimos el mensaje de la UI a un mensaje de whatsapp + const message: WhatsAppMessage = { + id: uiMessage.id, + body: uiMessage.text, + text: uiMessage.text, + type: uiMessage.type, + from: uiMessage.from, + to: uiMessage.to, + chatId: uiMessage.chatId, + timestamp: uiMessage.ts * 1000, // Convertir a milisegundos + fromMe: true, + viewed: false, + t: uiMessage.ts * 1000, + notifyName: 'User', + author: null, + invis: false, + isNewMsg: true, + star: false, + kicNotified: false, + recvFresh: true, + isFromTemplate: false, + pollInvalidated: false, + isSentCagPollCreation: false, + latestEditMsgKey: null, + latestEditSenderTimestampMs: null, + mentionedJidList: uiMessage.mentions || [], + groupMentions: [], + isEventCanceled: false, + eventInvalidated: false, + isVcardOverMmsDocument: false, + labels: [], + hasReaction: uiMessage.meta.hasReaction, + ephemeralDuration: 0, + ephemeralSettingTimestamp: 0, + disappearingModeInitiator: '', + disappearingModeTrigger: '', + viewMode: '', + productHeaderImageRejected: false, + lastPlaybackProgress: 0, + isDynamicReplyButtonsMsg: false, + isCarouselCard: false, + parentMsgId: null, + callSilenceReason: null, + isVideoCall: false, + callDuration: null, + callParticipants: null, + isMdHistoryMsg: false, + stickerSentTs: 0, + isAvatar: false, + lastUpdateFromServerTs: 0, + invokedBotWid: null, + bizBotType: null, + botResponseTargetId: null, + botPluginType: null, + botPluginReferenceIndex: null, + botPluginSearchProvider: null, + botPluginSearchUrl: null, + botPluginSearchQuery: null, + botPluginMaybeParent: false, + botReelPluginThumbnailCdnUrl: null, + botMessageDisclaimerText: null, + botMsgBodyType: null, + reportingTokenInfo: null, + requiresDirectConnection: false, + bizContentPlaceholderType: null, + hostedBizEncStateMismatch: false, + senderOrRecipientAccountTypeHosted: false, + placeholderCreatedWhenAccountIsHosted: false, + device: 0, + local: true, + mId: uiMessage.id, + senderId: uiMessage.from, + content: uiMessage.text, + isGroupMsg: false, + isQuotedMsgAvailable: uiMessage.meta.isQuoted, + isMedia: !!uiMessage.mediaUrl, + mediaData: uiMessage.mediaUrl ? { url: uiMessage.mediaUrl } : {}, + isOnline: false, + sender: { + id: uiMessage.from, + name: 'User', + shortName: 'User', + pushname: 'User', + type: 'user', + isBusiness: false, + isEnterprise: false, + isSmb: false, + isContactSyncCompleted: 1, + disappearingModeDuration: 0, + disappearingModeSettingTimestamp: 0, + textStatusLastUpdateTime: 0, + syncToAddressbook: false, + formattedName: 'User', + isMe: true, + isMyContact: false, + isPSA: false, + isUser: true, + isVerified: false, + isWAContact: true, + msgs: [] + }, + chat: { + id: uiMessage.chatId, + name: 'Chat', + isGroup: false, + participantsCount: 2, + formattedTitle: 'Chat', + pendingMsgs: false, + t: uiMessage.ts * 1000, + unreadCount: 0, + unreadDividerOffset: 0, + archive: false, + isReadOnly: false, + isLocked: false, + muteExpiration: 0, + isAutoMuted: false, + notSpam: true, + pin: 0, + ephemeralDuration: 0, + ephemeralSettingTimestamp: 0, + disappearingModeInitiator: '', + disappearingModeTrigger: '', + createdLocally: false, + unreadMentionsOfMe: [], + unreadMentionCount: 0, + hasUnreadMention: false, + archiveAtMentionViewedInDrawer: false, + hasChatBeenOpened: true, + tcToken: {}, + tcTokenTimestamp: 0, + tcTokenSenderTimestamp: 0, + endOfHistoryTransferType: 0, + pendingInitialLoading: false, + unreadEditTimestampMs: 0, + celebrationAnimationLastPlayed: 0, + hasRequestedWelcomeMsg: false, + canSend: true, + groupMetadata: {}, + isOnline: false, + contact: { + id: uiMessage.chatId, + name: 'User', + shortName: 'User', + pushname: 'User', + type: 'user', + isBusiness: false, + isEnterprise: false, + isSmb: false, + isContactSyncCompleted: 1, + disappearingModeDuration: 0, + disappearingModeSettingTimestamp: 0, + textStatusLastUpdateTime: 0, + syncToAddressbook: false, + formattedName: 'User', + isMe: true, + isMyContact: false, + isPSA: false, + isUser: true, + isVerified: false, + isWAContact: true, + msgs: [] + }, + msgs: [] + } + }; + + // nosotros mismos tenemos que buscar la conversacion in la memoria, conversations. si no existe, crearla + let conversation = conversations.get(uiMessage.chatId); + if (!conversation) { + conversation = { + chatId: uiMessage.chatId, + title: 'Chat', + isGroup: false, + unreadCount: 0, + participants: [], + messages: [], + createdAt: Date.now() + } + conversations.set(uiMessage.chatId, conversation); + } + processIncomingMessageForConversation(conversation, message); + + + const handler = getHandler(uiMessage.chatId); + if (!handler) throw new Error('No handler configured'); + let reply: string; + if (typeof handler === 'string') { + console.log(`🔗 Calling agent at ${handler} for conversation ${uiMessage.chatId}\n`); + + const agentRes = await axios.post(handler, { conversation }); + reply = agentRes.data.reply || agentRes.data; + } else { + reply = await handler(conversation); + } + + const replyMsg: Msg = { + id: uiMessage.id, + from: uiMessage.to, + to: uiMessage.from, + ts: uiMessage.ts, + type: 'chat', + text: reply, + mediaUrl: undefined, + mentions: [], + meta: { + ack: 0, + hasReaction: false, + isQuoted: false + } + }; + conversation.messages.push(replyMsg); + console.log('conversation', conversation); + + + // Devolver la conversación completa + /* it needs to be able to be parsed by the UI + const response = await axios.post(routerUrl, {data:messagePayload}); + console.log('Message sent, raw response:', response.data); // Log raw response + + const conversation = response.data; + */ + res.json(conversation); + + } catch (err: any) { + console.error('Error processing UI message:', err.message); + res.status(400).json({ error: err.message }); + } + }); +} diff --git a/whatsapp-router/src/routes/conversationActions.ts b/whatsapp-router/src/routes/conversationActions.ts index fa2ad80..574c819 100644 --- a/whatsapp-router/src/routes/conversationActions.ts +++ b/whatsapp-router/src/routes/conversationActions.ts @@ -43,4 +43,5 @@ export function registerConversationRoutes(app: Application, openWaUrl: string | console.log(`Conversation ${req.params.id} deleted: ${deleted}`); res.json({ success: deleted }); }); + } \ No newline at end of file