模板消息等

todo: 更新已发送的消息的状态. renderId
dev/chat
Lei OT 2 years ago
parent 70fbcae715
commit ef71252617

@ -1,19 +1,45 @@
export const replaceTemplateString = (str, replacements) => {
let result = str;
let keys = str.match(/{{(.*?)}}/g).map(key => key.replace(/{{|}}/g, ''));
for (let i = 0; i < keys.length; i++) {
let replaceValue = replacements[i];
let template = new RegExp(`{{${keys[i]}}}`, 'g');
result = result.replace(template, replaceValue);
}
return result;
}
/**
*
// +8618777396951 lyj
{
"to": "+8613317835586", // qqs
"msgtype": "text",
"msgcontent": "{\"body\":\"txtmsgtest\"}"
}
*/
export const sentMsgTypeMapped = { export const sentMsgTypeMapped = {
text: { text: {
type: 'text', type: 'text',
contentToSend: (msg) => ({ type: 'text', from: '+8617607730395', to: '', text: { body: msg.text } }), contentToSend: (msg) => ({ renderId: msg.id, to: msg.to, msgtype: 'text', msgcontent: { body: msg.text } }),
contentToRender: (msg) => ({...msg}), contentToRender: (msg) => ({ ...msg }),
}, },
whatsappTemplate: { whatsappTemplate: {
type: 'template', contentToSend: (msg) => ({ renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: msg.template }),
contentToSend: (msg) => ({ contentToRender: (msg) => {
template: { console.log(msg);
namespace: msg.whatsappTemplate.namespace, const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({...r, [v.type]: v}), {}) : null;
language: msg.whatsappTemplate.language, const templateParam = (templateDataMapped?.body?.parameters || []).map(e => e.text);
type: msg.whatsappTemplate.type, const fillTemplate = templateParam.length ? replaceTemplateString(msg.template_origin.components.body?.[0]?.text || '', templateParam) : (msg.template_origin.components.body?.[0]?.text || '');
components: msg.whatsappTemplate.components, return {
}, ...msg,
}), type: 'text',
title: msg.template_origin.components.header?.[0]?.text || '',
text: fillTemplate, // msg.template_origin.components.body?.[0]?.text || '',
};
},
}, },
}; };
export const whatsappMsgMapped = { export const whatsappMsgMapped = {
@ -23,23 +49,48 @@ export const whatsappMsgMapped = {
return result?.whatsappInboundMessage || null; return result?.whatsappInboundMessage || null;
}, },
contentToRender: (result) => { contentToRender: (result) => {
console.log( 'whatsapp.inbound_message.received', result); console.log('whatsapp.inbound_message.received to render', result);
const contentObj = result?.whatsappInboundMessage || result; // debug: const contentObj = result?.whatsappInboundMessage || result; // debug:
return parseRenderMessageItem(contentObj); return parseRenderMessageItem(contentObj);
}, },
}, },
'whatsapp.message.updated': {
getMsg: (result) => {
console.log('getMsg', result);
let contentObj = result?.whatsappMessage || null;
if ((contentObj?.status === 'failed' )) {
contentObj = {
type: 'error',
text: {body: 'Message failed to send' }, // contentObj.errorMessage
id: contentObj.id,
wamid: contentObj.id,
};
}
return contentObj;
},
contentToRender: (msgcontent) => {
const contentObj = msgcontent?.whatsappMessage || msgcontent; // debug:
console.log('whatsapp.message.updated to render', contentObj);
const _r = parseRenderMessageItem(contentObj);
console.log('_r', _r);
return parseRenderMessageItem(contentObj);
},
contentToUpdate: (msg) => ({ ...msg, id: msg.wamid, stauts: msg.status }),
},
}; };
export const whatsappMsgTypeMapped = { export const whatsappMsgTypeMapped = {
text: { type: 'text', data: (msg) => ({ text: msg.text.body }) }, error: {
type: (_m) => ({ type: 'system' }),
data: (msg) => ({ id: msg.wamid, text: msg.errorCode ? msg.errorMessage : msg.text.body }),
},
text: {
type: (msg) => ({ type: msg.errorCode ? 'system' : 'text' }),
data: (msg) => ({ id: msg.wamid, text: msg.errorCode ? msg.errorMessage : msg.text.body }),
},
image: { image: {
type: 'photo', type: 'photo',
data: (msg) => ({ data: (msg) => ({
data: { data: { id: msg.wamid, uri: msg.image.link, width: 200, height: 200, alt: '' },
uri: msg.image.link,
width: 200,
height: 200,
alt: '',
},
onOpen: () => { onOpen: () => {
console.log('Open image', msg.image.link); console.log('Open image', msg.image.link);
}, },
@ -48,18 +99,14 @@ export const whatsappMsgTypeMapped = {
sticker: { sticker: {
type: 'photo', type: 'photo',
data: (msg) => ({ data: (msg) => ({
data: { data: { id: msg.wamid, uri: msg.sticker.link, width: 150, height: 120, alt: '' },
uri: msg.sticker.link,
width: 150,
height: 120,
alt: '',
},
}), }),
}, },
video: { video: {
type: 'video', type: 'video',
data: (msg) => ({ data: (msg) => ({
data: { data: {
id: msg.wamid,
videoURL: msg.video.link, videoURL: msg.video.link,
status: { status: {
click: true, click: true,
@ -72,13 +119,25 @@ export const whatsappMsgTypeMapped = {
audio: { audio: {
type: 'audio', type: 'audio',
data: (msg) => ({ data: (msg) => ({
id: msg.wamid,
data: { data: {
audioURL: msg.audio.link, audioURL: msg.audio.link,
}, },
}), }),
}, },
'unsupported': { type: 'system', data: (msg) => ({ text: 'Message type is currently not supported.' }) }, unsupported: { type: 'system', data: (msg) => ({ text: 'Message type is currently not supported.' }) },
// 'unsupported': { type: 'text', data: (msg) => ({ text: 'Message type is currently not supported.' }) } reaction: {
type: 'text',
data: (msg) => ({ id: msg.wamid, text: msg.reaction?.emoji || msg.reaction?.text?.body || 'Reaction', reply: { message: '{content}', title: 'React from' } }),
},
template: {
type: (msg) => 'text',
data: (msg) => ({
id: msg.wamid,
// todo: update status: sent
status: msg.status,
}),
},
// file: 'file', // file: 'file',
// location: 'location', // location: 'location',
// contact: 'contact', // contact: 'contact',
@ -87,23 +146,24 @@ export const whatsappMsgTypeMapped = {
// 'contact-card-with-photo-and-label': 'contact-card-with-photo-and-label', // 'contact-card-with-photo-and-label': 'contact-card-with-photo-and-label',
}; };
export const parseRenderMessageItem = (msg) => { export const parseRenderMessageItem = (msg) => {
console.log(msg, '[[[['); console.log('parseRenderMessageItem', msg);
return { return {
date: msg?.sendTime || '',
...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}), ...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}),
id: msg.id, ...(whatsappMsgTypeMapped?.[msg.type]?.type(msg) || { type: 'text' }), // type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text',
sender: msg.from, sender: msg.from,
type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text', status: msg?.status || 'waiting',
// title: msg.customerProfile.name, // title: msg.customerProfile.name,
date: msg.sendTime, // replyButton: true,
}; };
}; };
export const parseRenderMessageList = (messages) => { export const parseRenderMessageList = (messages) => {
return messages.map((msg) => { return messages.map((msg) => {
return { return {
...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}), ...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}),
...(whatsappMsgTypeMapped?.[msg.type]?.type(msg) || { type: 'text' }), // type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text',
id: msg.id, id: msg.id,
sender: msg.from, sender: msg.from,
type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text',
// title: msg.customerProfile.name, // title: msg.customerProfile.name,
date: msg.sendTime, date: msg.sendTime,
}; };

@ -1,4 +1,4 @@
import React, { createContext, useContext, useState, useEffect } from 'react'; import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
import { RealTimeAPI } from '@/lib/realTimeAPI'; import { RealTimeAPI } from '@/lib/realTimeAPI';
import { whatsappMsgMapped, sentMsgTypeMapped, parseRenderMessageList } from '@/lib/msgUtils'; import { whatsappMsgMapped, sentMsgTypeMapped, parseRenderMessageList } from '@/lib/msgUtils';
import { groupBy } from '@/utils/utils'; import { groupBy } from '@/utils/utils';
@ -13,48 +13,77 @@ export async function fetchJSON(url, data) {
ifp = params ? '?' : ifp; ifp = params ? '?' : ifp;
} }
ifp = url.includes('?') ? '' : ifp; ifp = url.includes('?') ? '' : ifp;
const host = /^https?:\/\//i.test(url) ? '': ''; // HT_HOST; const host = /^https?:\/\//i.test(url) ? '' : ''; // HT_HOST;
const response = await fetch(`${host}${url}${ifp}${params}`); const response = await fetch(`${host}${url}${ifp}${params}`);
return await response.json(); return await response.json();
} }
// const API_HOST = 'http://202.103.68.144:8888'; // const API_HOST = 'http://202.103.68.144:8888';
const API_HOST = 'http://202.103.68.144:8888'; const API_HOST = 'http://202.103.68.144:8888';
const WS_URL = 'ws://202.103.68.144:8888/whatever/'; // const WS_URL = 'ws://202.103.68.144:8888/whatever/';
// const WS_URL = 'ws://202.103.68.157:8888/whatever/'; const WS_URL = 'ws://202.103.68.157:8888/whatever/';
// let realtimeAPI = new RealTimeAPI({ url: URL, protocol: 'aaa' }); // let realtimeAPI = new RealTimeAPI({ url: URL, protocol: 'aaa' });
let realtimeAPI = new RealTimeAPI({ url: WS_URL, protocol: 'WhatsApp' }); let realtimeAPI = new RealTimeAPI({ url: WS_URL, protocol: 'WhatsApp' });
export const useConversations = () => { export const useConversations = () => {
const [errors, setErrors] = useState([]); const [errors, setErrors] = useState([]);
const [messages, setMessages] = useState([]); // active conversation const [messages, setMessages] = useState([]); // active conversation
const [conversations, setConversations] = useState({}); // all conversation const [activeConversations, setActiveConversations] = useState({}); // all active conversation
const [currentID, setCurrentID] = useState(); const [currentID, setCurrentID] = useState();
const [conversationsList, setConversationsList] = useState([]); // open conversations const [conversationsList, setConversationsList] = useState([]); // open conversations
const [currentConversation, setCurrentConversation] = useState({ const [currentConversation, setCurrentConversation] = useState({
id: '', name: '' id: '',
name: '',
}); });
const currentConversationRef = useRef(currentConversation);
useEffect(() => {
currentConversationRef.current = currentConversation;
}, [currentConversation]);
useEffect(() => {
realtimeAPI.onError(addError.bind(null, 'Error'));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server'));
realtimeAPI.keepAlive(); // Ping Server
// Cleanup function to remove the event listeners when the component is unmounted
return () => {
realtimeAPI.disconnect();
};
}, []);
useEffect(() => { useEffect(() => {
getConversations(); getConversationsList();
// getTemplates(); // getTemplates();
return () => {}; return () => {};
}, []); }, []);
const getConversations = async () => { useEffect(() => {
if (!currentConversation.id && conversationsList.length > 0) {
switchConversation(conversationsList[0]);
}
return () => {};
}, [conversationsList]);
const getConversationsList = async () => {
const data = await fetchJSON('http://127.0.0.1:4523/m2/3888351-0-default/142426823'); const data = await fetchJSON('http://127.0.0.1:4523/m2/3888351-0-default/142426823');
const dataMapped = data.reduce((r, v) => ({ ...r, [v.id]: [] }), {});
setConversationsList(data); setConversationsList(data);
if (data && data.length) { setActiveConversations({...dataMapped, ...activeConversations});
switchConversation(data[0]); console.log(data, dataMapped);
} // if (data && data.length) {
// switchConversation(data[0]);
// }
}; };
const templates = AllTemplates.filter(_t => _t.status !== 'REJECTED').map( ele => ({...ele, components: groupBy(ele.components, _c => _c.type.toLowerCase())})); // test: 0 const templates = AllTemplates.filter((_t) => _t.status !== 'REJECTED').map((ele) => ({ ...ele, components: groupBy(ele.components, (_c) => _c.type.toLowerCase()) })); // test: 0
// const [templates, setTemplates] = useState([]); // const [templates, setTemplates] = useState([]);
const [templatesList, setTemplatesList] = useState([]); const [templatesList, setTemplatesList] = useState([]);
const getTemplates = async () => { const getTemplates = async () => {
const data = await fetchJSON(`${API_HOST}/listtemplates`); const data = await fetchJSON(`${API_HOST}/listtemplates`);
const canUseTemplates = (data?.result?.items || []).filter(_t => _t.status !== 'REJECTED'); const canUseTemplates = (data?.result?.items || []).filter((_t) => _t.status !== 'REJECTED');
setTemplatesList(canUseTemplates); setTemplatesList(canUseTemplates);
}; };
@ -66,36 +95,85 @@ export const useConversations = () => {
}; };
const switchConversation = (cc) => { const switchConversation = (cc) => {
console.log('switch to ', cc.id, cc);
setCurrentID(cc.id); setCurrentID(cc.id);
setCurrentConversation(cc); setCurrentConversation(cc);
// debug: 0 const _all = [];
const _all = all.map((ele) => whatsappMsgMapped['whatsapp.inbound_message.received'].contentToRender(ele)); // const _all = all.map((ele) => whatsappMsgMapped['whatsapp.inbound_message.received'].contentToRender(ele)); // debug: 0
setMessages([..._all,...conversations[cc.id] || []]); // todo: update msg status
// const _all = all2.map((ele) => whatsappMsgMapped['whatsapp.message.updated'].contentToRender(ele)); // debug: 0
setMessages([..._all, ...(activeConversations[cc.id] || [])]);
// Get customer profile when switching conversation // Get customer profile when switching conversation
getCustomerProfile(cc.id); // getCustomerProfile(cc.id);
}; };
useEffect(() => {
return () => {
getCustomerProfile(currentID);
};
}, [currentID]);
/** /**
* *****************************************************************************************************
* websocket -------------------------------------------------------------------------------------------- * websocket --------------------------------------------------------------------------------------------
* *****************************************************************************************************
*/ */
const addError = (reason) => { const addError = (reason) => {
setErrors(prevErrors => [...prevErrors, { reason }]); setErrors((prevErrors) => [...prevErrors, { reason }]);
} };
const addMessageToConversations = (customerId, message) => { const addMessageToConversations = (customerId, message) => {
setConversations((prevList) => ({ // setActiveConversations((prevList) => ({
...prevList, // ...prevList,
[customerId]: [...(prevList[customerId] || []), message], // [customerId]: [...(prevList[customerId] || []), message],
})); // }));
// console.log('activeConversations', activeConversations);
setActiveConversations((prevList) => {
const updatedList = {...prevList};
if(updatedList[customerId]) {
updatedList[customerId].push(message);
} else {
updatedList[customerId] = [message];
}
return updatedList;
});
}; };
useEffect(() => {
console.log(messages, 'messages');
return () => {
}
}, [messages])
const addMessage = (message) => { const addMessage = (message) => {
setMessages((prevMessages) => [ ...prevMessages, message]); setMessages((prevMessages) => [...prevMessages, message]);
addMessageToConversations(currentConversation.id, message); addMessageToConversations(currentConversationRef.current.id, message);
};
const updateMessage = (message) => {
setMessages((prevMessages) => {
return prevMessages.map(ele => {
if (ele.id === message.id) {
return {...ele, status: message.status};
}
return ele;
});
// const updatedList = [...prevMessages];
// const index = prevMessages.findIndex((_m) => _m.id === message.id);
// if (index !== -1) {
// updatedList[index] = message;
// }
// return updatedList;
});
}; };
const handleMessage = (data) => { const handleMessage = (data) => {
console.log('handleMessage------------------', );
/**
* ! event handlers in JavaScript capture the environment (including variables) at the time they are defined, not when they are executed.
*/
const { errcode, errmsg, result } = data; const { errcode, errmsg, result } = data;
if (!result) { if (!result) {
@ -104,293 +182,351 @@ export const useConversations = () => {
if (typeof result.type === 'string' && result.type === 'error') { if (typeof result.type === 'string' && result.type === 'error') {
addError('Error Connecting to Server'); addError('Error Connecting to Server');
} }
console.log(result, 'handleMessage------------------'); console.log(result.type, 'result.type');
const msgObj = whatsappMsgMapped[result.type].getMsg(result); const msgObj = whatsappMsgMapped[result.type].getMsg(result);
console.log(msgObj, 'msgObj'); const msgRender = whatsappMsgMapped[result.type].contentToRender(msgObj);
addMessage(whatsappMsgMapped[result.type].contentToRender(msgObj)); const msgUpdate = whatsappMsgMapped[result.type].contentToUpdate(msgObj);
console.log('msgRender', msgRender, msgUpdate);
if (result.type === 'whatsapp.message.updated') {
updateMessage(msgRender);
return false;
}
addMessage(msgRender);
console.log('handleMessage*******************', );
}; };
const sendMessage = (msgObj) => { const sendMessage = (msgObj) => {
const contentToSend = sentMsgTypeMapped[msgObj.type].contentToSend(msgObj); const contentToSend = sentMsgTypeMapped[msgObj.type].contentToSend(msgObj);
realtimeAPI.sendMessage(contentToSend); realtimeAPI.sendMessage(contentToSend);
const contentToRender = sentMsgTypeMapped[msgObj.type].contentToRender(msgObj); const contentToRender = sentMsgTypeMapped[msgObj.type].contentToRender(msgObj);
console.log(contentToRender, 'contentToRender sendMessage------------------');
addMessage(contentToRender); addMessage(contentToRender);
}; };
realtimeAPI.onError(addError.bind(null, 'Error'));
realtimeAPI.onMessage(handleMessage);
realtimeAPI.onCompletion(addError.bind(null, 'Not Connected to Server'));
realtimeAPI.keepAlive(); // Ping Server
return { return {
errors, messages, conversationsList, currentConversation, sendMessage, errors,
getConversations, switchConversation, messages,
conversationsList,
currentConversation,
sendMessage,
getConversationsList,
switchConversation,
// templates: templatesList, // setTemplates, getTemplates, // templates: templatesList, // setTemplates, getTemplates,
templates, // debug: 0 templates, // debug: 0
customerOrderProfile, customerOrderProfile,
}; };
} };
// test: 0 "type": "whatsapp.inbound_message.received",
// test: 0
const all = [ const all = [
// {
// 'id': '65b06828619a1d82777eb4c6',
// 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBQzNDNzBFNjFCREJBNDIyQjQ2AA==',
// 'wabaId': '190290134156880',
// 'from': '+8613317835586',
// 'customerProfile': {
// 'name': 'qqs',
// },
// 'to': '+8617607730395',
// 'sendTime': '2024-01-24T01:30:14.000Z',
// 'type': 'image',
// 'image': {
// 'link':
// 'https://api.ycloud.com/v2/whatsapp/media/download/934379820978291?sig=t%3D1706059814%2Cs%3D91a79a0e4007ad2f6a044a28307affe663f7f81903b3537bd80e758d3c0d0563&payload=eyJpZCI6IjkzNDM3OTgyMDk3ODI5MSIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNjgyODYxOWExZDgyNzc3ZWI0YzYiLCJtaW1lVHlwZSI6ImltYWdlL2pwZWciLCJzaGEyNTYiOiJPVTJjdkN2eHplMUdMMmQ5NUxyTGVaNmpNb2ZscUZYM1RvcXdTTUNWZkxNPSJ9',
// 'id': '934379820978291',
// 'sha256': 'OU2cvCvxze1GL2d95LrLeZ6jMoflqFX3ToqwSMCVfLM=',
// 'mime_type': 'image/jpeg',
// },
// },
// {
// 'id': '65b06ce6619a1d8277c97fc0',
// 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBMUJBOUZCODY4NkNBMkM2NUEzAA==',
// 'wabaId': '190290134156880',
// 'from': '+8613317835586',
// 'customerProfile': {
// 'name': 'qqs',
// },
// 'to': '+8617607730395',
// 'sendTime': '2024-01-24T01:50:29.000Z',
// 'type': 'text',
// 'text': {
// 'body': 'eeee',
// },
// },
// {
// 'id': '65b06b2f619a1d8277b5ab06',
// 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRkU0RUZGRUI1OUQzQUFBMEExAA==',
// 'wabaId': '190290134156880',
// 'from': '+8613317835586',
// 'customerProfile': {
// 'name': 'qqs',
// },
// 'to': '+8617607730395',
// 'sendTime': '2024-01-24T01:43:09.000Z',
// 'type': 'audio',
// 'audio': {
// 'link':
// 'https://api.ycloud.com/v2/whatsapp/media/download/901696271448320?sig=t%3D1706060589%2Cs%3Dca75dbd57e4867783390c913491263f07c9738d69c141d4ae622c76df9fa033b&payload=eyJpZCI6IjkwMTY5NjI3MTQ0ODMyMCIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNmIyZjYxOWExZDgyNzdiNWFiMDYiLCJtaW1lVHlwZSI6ImF1ZGlvL29nZzsgY29kZWNzPW9wdXMiLCJzaGEyNTYiOiJoZUNSUDdEMjM3bG9ydkZ4eFhSdHZpU1ZsNDR3Rlk4TytaMFhic2k5cy9rPSJ9',
// 'id': '901696271448320',
// 'sha256': 'heCRP7D237lorvFxxXRtviSVl44wFY8O+Z0Xbsi9s/k=',
// 'mime_type': 'audio/ogg; codecs=opus',
// },
// },
// {
// 'id': '65b06b12619a1d8277b3c0c4',
// 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBREZEMEM0MURDNjJGREVEQjY3AA==',
// 'wabaId': '190290134156880',
// 'from': '+8613317835586',
// 'customerProfile': {
// 'name': 'qqs',
// },
// 'to': '+8617607730395',
// 'sendTime': '2024-01-24T01:42:40.000Z',
// 'type': 'video',
// 'video': {
// 'link':
// 'https://api.ycloud.com/v2/whatsapp/media/download/742404324517058?sig=t%3D1706060560%2Cs%3D53eeb1508c2103e310fb14a72563a8e07c5a84c7e6192a25f3608ac9bea32334&payload=eyJpZCI6Ijc0MjQwNDMyNDUxNzA1OCIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNmIxMjYxOWExZDgyNzdiM2MwYzQiLCJtaW1lVHlwZSI6InZpZGVvL21wNCIsInNoYTI1NiI6IlNJcjRlZFlPb1BDTGtETEgrVTY2d3dkMDgra2JndFV5OHRDd2RjQU5FaFU9In0',
// 'caption': 'and',
// 'id': '742404324517058',
// 'sha256': 'SIr4edYOoPCLkDLH+U66wwd08+kbgtUy8tCwdcANEhU=',
// 'mime_type': 'video/mp4',
// },
// },
// {
// 'id': '65b06aa7619a1d8277ac806e',
// 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBOTFBOTU5RDE2QjgxQTQ1MEE2AA==',
// 'wabaId': '190290134156880',
// 'from': '+8613317835586',
// 'customerProfile': {
// 'name': 'qqs',
// },
// 'to': '+8617607730395',
// 'sendTime': '2024-01-24T01:40:53.000Z',
// 'type': 'sticker',
// 'sticker': {
// 'link':
// 'https://api.ycloud.com/v2/whatsapp/media/download/1156118002042289?sig=t%3D1706060453%2Cs%3Dfbd5f881856614e35715b1e3e1097b3bbe56f8a36aaa67bfbef25a37d9143d51&payload=eyJpZCI6IjExNTYxMTgwMDIwNDIyODkiLCJ3YWJhSWQiOiIxOTAyOTAxMzQxNTY4ODAiLCJpbmJvdW5kTWVzc2FnZUlkIjoiNjViMDZhYTc2MTlhMWQ4Mjc3YWM4MDZlIiwibWltZVR5cGUiOiJpbWFnZS93ZWJwIiwic2hhMjU2IjoibUNaLzdhNnNaNlRNYTE0WW9rUkNTZnVsdGpZNmFRRVZFNVoxMVRwanNQOD0ifQ',
// 'id': '1156118002042289',
// 'sha256': 'mCZ/7a6sZ6TMa14YokRCSfultjY6aQEVE5Z11TpjsP8=',
// 'mime_type': 'image/webp',
// 'animated': false,
// },
// },
// {
// 'id': '65b06a91619a1d8277aaf05e',
// 'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRjUxNzdCQ0FEOTlFQzc5MzQ1AA==',
// 'wabaId': '190290134156880',
// 'from': '+8613317835586',
// 'customerProfile': {
// 'name': 'qqs',
// },
// 'to': '+8617607730395',
// 'sendTime': '2024-01-24T01:40:32.000Z',
// 'type': 'unsupported',
// 'errors': [
// {
// 'code': '131051',
// 'title': 'Message type unknown',
// 'message': 'Message type unknown',
// 'error_data': {
// 'details': 'Message type is currently not supported.',
// },
// },
// ],
// },
];
const all2 = [
// {
// 'id': '63f71fb8741c165b434292fb',
// 'wamid': 'wamid.HBgNOD...',
// 'wabaId': 'WABA-ID',
// 'from': 'CUSTOMER-PHONE-NUMBER',
// 'customerProfile': {
// 'name': 'Joe',
// },
// 'to': 'BUSINESS-PHONE-NUMBER',
// 'sendTime': '2023-02-22T12:00:00.000Z',
// 'type': 'reaction',
// 'reaction': {
// 'message_id': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBMUJBOUZCODY4NkNBMkM2NUEzAA==',
// 'emoji': '👍',
// },
// },
{ {
'id': '65b06828619a1d82777eb4c6', 'id': '65b1de2f3f0bb66a91377930',
'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBQzNDNzBFNjFCREJBNDIyQjQ2AA==', 'wamid': 'wamid.HBgNODYxODc3NzM5Njk1MRUCABEYEkM4NTU5MjMyRDFCRkE5NjM2RAA=',
'wabaId': '190290134156880', 'status': 'sent',
'from': '+8613317835586', 'from': '+8617607730395',
'customerProfile': { 'to': '+8618777396951',
'name': 'qqs',
},
'to': '+8617607730395',
'sendTime': '2024-01-24T01:30:14.000Z',
'type': 'image',
'image': {
'link':
'https://api.ycloud.com/v2/whatsapp/media/download/934379820978291?sig=t%3D1706059814%2Cs%3D91a79a0e4007ad2f6a044a28307affe663f7f81903b3537bd80e758d3c0d0563&payload=eyJpZCI6IjkzNDM3OTgyMDk3ODI5MSIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNjgyODYxOWExZDgyNzc3ZWI0YzYiLCJtaW1lVHlwZSI6ImltYWdlL2pwZWciLCJzaGEyNTYiOiJPVTJjdkN2eHplMUdMMmQ5NUxyTGVaNmpNb2ZscUZYM1RvcXdTTUNWZkxNPSJ9',
'id': '934379820978291',
'sha256': 'OU2cvCvxze1GL2d95LrLeZ6jMoflqFX3ToqwSMCVfLM=',
'mime_type': 'image/jpeg',
},
},
{
'id': '65b06ce6619a1d8277c97fc0',
'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBMUJBOUZCODY4NkNBMkM2NUEzAA==',
'wabaId': '190290134156880',
'from': '+8613317835586',
'customerProfile': {
'name': 'qqs',
},
'to': '+8617607730395',
'sendTime': '2024-01-24T01:50:29.000Z',
'type': 'text',
'text': {
'body': 'eeee',
},
},
{
'id': '65b06b2f619a1d8277b5ab06',
'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRkU0RUZGRUI1OUQzQUFBMEExAA==',
'wabaId': '190290134156880',
'from': '+8613317835586',
'customerProfile': {
'name': 'qqs',
},
'to': '+8617607730395',
'sendTime': '2024-01-24T01:43:09.000Z',
'type': 'audio',
'audio': {
'link':
'https://api.ycloud.com/v2/whatsapp/media/download/901696271448320?sig=t%3D1706060589%2Cs%3Dca75dbd57e4867783390c913491263f07c9738d69c141d4ae622c76df9fa033b&payload=eyJpZCI6IjkwMTY5NjI3MTQ0ODMyMCIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNmIyZjYxOWExZDgyNzdiNWFiMDYiLCJtaW1lVHlwZSI6ImF1ZGlvL29nZzsgY29kZWNzPW9wdXMiLCJzaGEyNTYiOiJoZUNSUDdEMjM3bG9ydkZ4eFhSdHZpU1ZsNDR3Rlk4TytaMFhic2k5cy9rPSJ9',
'id': '901696271448320',
'sha256': 'heCRP7D237lorvFxxXRtviSVl44wFY8O+Z0Xbsi9s/k=',
'mime_type': 'audio/ogg; codecs=opus',
},
},
{
'id': '65b06b12619a1d8277b3c0c4',
'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBREZEMEM0MURDNjJGREVEQjY3AA==',
'wabaId': '190290134156880',
'from': '+8613317835586',
'customerProfile': {
'name': 'qqs',
},
'to': '+8617607730395',
'sendTime': '2024-01-24T01:42:40.000Z',
'type': 'video',
'video': {
'link':
'https://api.ycloud.com/v2/whatsapp/media/download/742404324517058?sig=t%3D1706060560%2Cs%3D53eeb1508c2103e310fb14a72563a8e07c5a84c7e6192a25f3608ac9bea32334&payload=eyJpZCI6Ijc0MjQwNDMyNDUxNzA1OCIsIndhYmFJZCI6IjE5MDI5MDEzNDE1Njg4MCIsImluYm91bmRNZXNzYWdlSWQiOiI2NWIwNmIxMjYxOWExZDgyNzdiM2MwYzQiLCJtaW1lVHlwZSI6InZpZGVvL21wNCIsInNoYTI1NiI6IlNJcjRlZFlPb1BDTGtETEgrVTY2d3dkMDgra2JndFV5OHRDd2RjQU5FaFU9In0',
'caption': 'and',
'id': '742404324517058',
'sha256': 'SIr4edYOoPCLkDLH+U66wwd08+kbgtUy8tCwdcANEhU=',
'mime_type': 'video/mp4',
},
},
{
'id': '65b06aa7619a1d8277ac806e',
'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBOTFBOTU5RDE2QjgxQTQ1MEE2AA==',
'wabaId': '190290134156880', 'wabaId': '190290134156880',
'from': '+8613317835586', 'type': 'template',
'customerProfile': { 'template': {
'name': 'qqs', 'name': 'hello',
'language': {
'code': 'zh_CN',
},
}, },
'to': '+8617607730395', 'conversation': {
'sendTime': '2024-01-24T01:40:53.000Z', 'id': 'ddb659076d0e970f16fb19520c116a1b',
'type': 'sticker', 'originType': 'marketing',
'sticker': { 'expireTime': '2024-01-26T04:07:00.000Z',
'link':
'https://api.ycloud.com/v2/whatsapp/media/download/1156118002042289?sig=t%3D1706060453%2Cs%3Dfbd5f881856614e35715b1e3e1097b3bbe56f8a36aaa67bfbef25a37d9143d51&payload=eyJpZCI6IjExNTYxMTgwMDIwNDIyODkiLCJ3YWJhSWQiOiIxOTAyOTAxMzQxNTY4ODAiLCJpbmJvdW5kTWVzc2FnZUlkIjoiNjViMDZhYTc2MTlhMWQ4Mjc3YWM4MDZlIiwibWltZVR5cGUiOiJpbWFnZS93ZWJwIiwic2hhMjU2IjoibUNaLzdhNnNaNlRNYTE0WW9rUkNTZnVsdGpZNmFRRVZFNVoxMVRwanNQOD0ifQ',
'id': '1156118002042289',
'sha256': 'mCZ/7a6sZ6TMa14YokRCSfultjY6aQEVE5Z11TpjsP8=',
'mime_type': 'image/webp',
'animated': false,
}, },
'createTime': '2024-01-25T04:06:07.850Z',
'updateTime': '2024-01-25T04:06:09.151Z',
'sendTime': '2024-01-25T04:06:08.000Z',
'totalPrice': 0.0782,
'currency': 'USD',
'bizType': 'whatsapp',
}, },
];
const templateExample = [
{ {
'id': '65b06a91619a1d8277aaf05e', 'id': 'evt_eEMtA0PkkyACiS5o',
'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRjUxNzdCQ0FEOTlFQzc5MzQ1AA==', 'type': 'whatsapp.template.reviewed',
'wabaId': '190290134156880', 'apiVersion': 'v2',
'from': '+8613317835586', 'createTime': '2023-02-20T12:00:00.000Z',
'customerProfile': { 'whatsappTemplate': {
'name': 'qqs', 'wabaId': 'WABA-ID',
'name': 'template_name',
'language': 'en',
'category': 'MARKETING',
'status': 'APPROVED',
'reason': 'NONE',
'statusUpdateEvent': 'APPROVED',
}, },
'to': '+8617607730395',
'sendTime': '2024-01-24T01:40:32.000Z',
'type': 'unsupported',
'errors': [
{
'code': '131051',
'title': 'Message type unknown',
'message': 'Message type unknown',
'error_data': {
'details': 'Message type is currently not supported.',
},
},
],
}, },
]; ];
const AllTemplates = [ const AllTemplates = [
{ {
"wabaId": "190290134156880", 'wabaId': '190290134156880',
"name": "say_hi", 'name': 'say_hi',
"language": "en", 'language': 'en',
"components": [ 'components': [
{ {
"type": "BODY", 'type': 'BODY',
"text": "Hi {{customer_name}} I'm {{your_name}} from Asia Highlights.\n\nWe provide *families* and *couples* with personalized and stress-free experiences, whether for _milestone trips_, _birthday trips_, _graduation trips_, or _bucketlist trips_.", 'text':
"example": { "Hi {{customer_name}} I'm {{your_name}} from Asia Highlights.\n\nWe provide *families* and *couples* with personalized and stress-free experiences, whether for _milestone trips_, _birthday trips_, _graduation trips_, or _bucketlist trips_.",
"body_text": [ 'example': {
[ 'body_text': [['Mike', 'Jimmy']],
"Mike", },
"Jimmy"
]
]
}
}, },
{ {
"type": "BUTTONS", 'type': 'BUTTONS',
"buttons": [ 'buttons': [
{ {
"type": "URL", 'type': 'URL',
"text": "Asia Highlights", 'text': 'Asia Highlights',
"url": "https://www.asiahighlights.com/" 'url': 'https://www.asiahighlights.com/',
} },
] ],
} },
], ],
"category": "UTILITY", 'category': 'UTILITY',
"status": "REJECTED", 'status': 'REJECTED',
"qualityRating": "UNKNOWN", 'qualityRating': 'UNKNOWN',
"reason": "INCORRECT_CATEGORY", 'reason': 'INCORRECT_CATEGORY',
"createTime": "2024-01-23T02:26:33.012Z", 'createTime': '2024-01-23T02:26:33.012Z',
"updateTime": "2024-01-23T02:26:35.397Z", 'updateTime': '2024-01-23T02:26:35.397Z',
"statusUpdateEvent": "REJECTED" 'statusUpdateEvent': 'REJECTED',
}, },
{ {
"wabaId": "190290134156880", 'wabaId': '190290134156880',
"name": "i_hope_this_message_finds_you_well", 'name': 'i_hope_this_message_finds_you_well',
"language": "en", 'language': 'en',
"components": [ 'components': [
{ {
"type": "BODY", 'type': 'BODY',
"text": "Hi {{customer_name}}, I hope this message finds you well. Did you see my previous message?", 'text': 'Hi {{customer_name}}, I hope this message finds you well. Did you see my previous message?',
"example": { 'example': {
"body_text": [ 'body_text': [['Mike']],
[ },
"Mike" },
]
]
}
}
], ],
"category": "UTILITY", 'category': 'UTILITY',
"status": "REJECTED", 'status': 'REJECTED',
"qualityRating": "UNKNOWN", 'qualityRating': 'UNKNOWN',
"reason": "INCORRECT_CATEGORY", 'reason': 'INCORRECT_CATEGORY',
"createTime": "2024-01-23T02:22:20.232Z", 'createTime': '2024-01-23T02:22:20.232Z',
"updateTime": "2024-01-23T02:22:22.937Z", 'updateTime': '2024-01-23T02:22:22.937Z',
"statusUpdateEvent": "REJECTED" 'statusUpdateEvent': 'REJECTED',
}, },
{ {
"wabaId": "190290134156880", 'wabaId': '190290134156880',
"name": "asia_highlights_has_receive_your_inquiry", 'name': 'asia_highlights_has_receive_your_inquiry',
"language": "en", 'language': 'en',
"components": [ 'components': [
{ {
"type": "BODY", 'type': 'BODY',
"text": "Dear {{customer_name}},\n\nThank you for choosing Asia Highlights. Your inquiry has been submitted to Asia Highlights. One of our travel advisors will respond within 24 hours.", 'text':
"example": { 'Dear {{customer_name}},\n\nThank you for choosing Asia Highlights. Your inquiry has been submitted to Asia Highlights. One of our travel advisors will respond within 24 hours.',
"body_text": [ 'example': {
[ 'body_text': [['Jimmy Liow']],
"Jimmy Liow" },
]
]
}
}, },
{ {
"type": "HEADER", 'type': 'HEADER',
"format": "TEXT", 'format': 'TEXT',
"text": "Asia highlights has receive your inquiry" 'text': 'Asia highlights has receive your inquiry',
}, },
{ {
"type": "FOOTER", 'type': 'FOOTER',
"text": "Kind regards, Asia Highlights Team" 'text': 'Kind regards, Asia Highlights Team',
}, },
{ {
"type": "BUTTONS", 'type': 'BUTTONS',
"buttons": [ 'buttons': [
{ {
"type": "URL", 'type': 'URL',
"text": "Asia Highlights", 'text': 'Asia Highlights',
"url": "https://www.asiahighlights.com/" 'url': 'https://www.asiahighlights.com/',
} },
] ],
} },
], ],
"category": "UTILITY", 'category': 'UTILITY',
"status": "APPROVED", 'status': 'APPROVED',
"qualityRating": "UNKNOWN", 'qualityRating': 'UNKNOWN',
"reason": "NONE", 'reason': 'NONE',
"createTime": "2024-01-19T05:59:32.933Z", 'createTime': '2024-01-19T05:59:32.933Z',
"updateTime": "2024-01-19T05:59:55.581Z", 'updateTime': '2024-01-19T05:59:55.581Z',
"statusUpdateEvent": "APPROVED" 'statusUpdateEvent': 'APPROVED',
}, },
{ {
"wabaId": "190290134156880", 'wabaId': '190290134156880',
"name": "hello", 'name': 'hello',
"language": "zh_CN", 'language': 'zh_CN',
"components": [ 'components': [
{ {
"type": "BODY", 'type': 'BODY',
"text": "你好,这是一个测试程序" 'text': '你好,这是一个测试程序',
}, },
{ {
"type": "HEADER", 'type': 'HEADER',
"format": "TEXT", 'format': 'TEXT',
"text": "Hello 同学" 'text': 'Hello 同学',
}, },
{ {
"type": "FOOTER", 'type': 'FOOTER',
"text": "Global Highlights" 'text': 'Global Highlights',
}, },
{ {
"type": "BUTTONS", 'type': 'BUTTONS',
"buttons": [ 'buttons': [
{ {
"type": "URL", 'type': 'URL',
"text": "about us", 'text': 'about us',
"url": "https://www.globalhighlights.com/" 'url': 'https://www.globalhighlights.com/',
} },
] ],
} },
], ],
"category": "MARKETING", 'category': 'MARKETING',
"status": "APPROVED", 'status': 'APPROVED',
"qualityRating": "UNKNOWN", 'qualityRating': 'UNKNOWN',
"reason": "NONE", 'reason': 'NONE',
"createTime": "2023-11-17T03:26:10.961Z", 'createTime': '2023-11-17T03:26:10.961Z',
"updateTime": "2023-11-17T13:36:33.623Z", 'updateTime': '2023-11-17T13:36:33.623Z',
"statusUpdateEvent": "APPROVED" 'statusUpdateEvent': 'APPROVED',
} },
]; ];

@ -4,11 +4,12 @@ import { Input, Button, Tabs, List, Space, Popover, Flex } from 'antd';
// import { Input } from 'react-chat-elements'; // import { Input } from 'react-chat-elements';
import { useConversationContext } from '@/stores/ConversationContext'; import { useConversationContext } from '@/stores/ConversationContext';
import { LikeOutlined, MessageOutlined, StarOutlined, SendOutlined, PlusOutlined, PlusCircleOutlined } from '@ant-design/icons'; import { LikeOutlined, MessageOutlined, StarOutlined, SendOutlined, PlusOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { isEmpty } from '@/utils/utils';
const InputBox = observer(({ onSend }) => { const InputBox = observer(({ onSend }) => {
const { templates } = useConversationContext(); const { currentConversation, templates } = useConversationContext();
const [textContent, setTextContent] = useState(''); const [textContent, setTextContent] = useState('');
console.log( 'ttt'); console.log('ttt');
const handleSendText = () => { const handleSendText = () => {
// console.log(textContent); // console.log(textContent);
@ -17,39 +18,82 @@ const InputBox = observer(({ onSend }) => {
type: 'text', type: 'text',
text: textContent, text: textContent,
sender: 'me', sender: 'me',
from: '', // from: '',
to: currentConversation.channel_id,
id: Date.now().toString(16), id: Date.now().toString(16),
date: new Date(), date: new Date(),
readState: false, status: 'waiting',
// renderAddCmp: () => (
// <Button size={'small'} type='link' key={'send'} icon={<SendOutlined />}>
// test
// </Button>
// ),
// statusTitle: 'Ready to send',
// removeButton: true,
// replyButtom: true,
// focus: true,
}; };
onSend(msgObj); onSend(msgObj);
setTextContent(''); setTextContent('');
} }
}; };
const handleSendTemplate = () => { }; const handleSendTemplate = (fromTemplate) => {
console.log(fromTemplate, 'fromTemplate');
if (typeof onSend === 'function') {
const msgObj = {
type: 'whatsappTemplate',
to: currentConversation.channel_id,
id: Date.now().toString(16),
date: new Date(),
status: 'waiting',
statusTitle: 'Ready to send',
sender: 'me',
template: {
name: fromTemplate.name,
language: { code: fromTemplate.language },
...(fromTemplate.components.body[0]?.example?.body_text?.[0]?.length > 0
? { components: [
{
'type': 'body',
'parameters': [
{
'type': 'text',
'text': currentConversation.name,
},
{
'type': 'text',
'text': currentConversation.name,
},
],
},
], }
: {}),
},
template_origin: fromTemplate,
};
onSend(msgObj);
setOpenTemplates(false);
}
};
const [openTemplates, setOpenTemplates] = useState(false); const [openTemplates, setOpenTemplates] = useState(false);
const handleOpenChange = (newOpen) => { const handleOpenChange = (newOpen) => {
setOpenTemplates(newOpen); setOpenTemplates(newOpen);
}; };
const onSendTemplates = () => {
setOpenTemplates(false);
// todo: send
}
return ( return (
<div> <div>
<Flex gap={8}> <Flex gap={8}>
<Popover <Popover
content={ content={
<List className='w-96 h-4/6 overflow-y-auto text-slate-900' <List
className='w-96 h-4/6 overflow-y-auto text-slate-900'
itemLayout='horizontal' itemLayout='horizontal'
dataSource={templates} dataSource={templates}
renderItem={(item, index) => ( renderItem={(item, index) => (
<List.Item <List.Item
actions={[ actions={[
<Button onClick={onSendTemplates} size={'small'} type='link' key={'send'} icon={<SendOutlined />}> <Button onClick={() => handleSendTemplate(item)} size={'small'} type='link' key={'send'} icon={<SendOutlined />}>
Send Send
</Button>, </Button>,
]}> ]}>

@ -81,7 +81,7 @@ const Messages = observer(() => {
const messagesEndRef = useRef(null); const messagesEndRef = useRef(null);
useEffect(() => { useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messagesList.length]); }, [messagesList.length]);
const [previewVisible, setPreviewVisible] = useState(false); const [previewVisible, setPreviewVisible] = useState(false);
@ -102,11 +102,12 @@ const Messages = observer(() => {
<div> <div>
{messagesList.map((message, index) => ( {messagesList.map((message, index) => (
<MessageBox <MessageBox
className={message.sender === 'me' ? 'whatsappme-container' : ''}
key={message.id} key={message.id}
position={message.sender === 'me' ? 'right' : 'left'} position={message.sender === 'me' ? 'right' : 'left'}
{...message} {...message}
onOpen={() => handlePreview(message)} onOpen={() => handlePreview(message)}
status={message.sender === 'me' ? 'sent' : ''} letterItem={{ id: 1, letter: 'AS' }}
// read | 'waiting'| 'sent' | 'received' | 'read' // read | 'waiting'| 'sent' | 'received' | 'read'
/> />
))} ))}

@ -19,6 +19,17 @@
padding: .5em; padding: .5em;
} }
/** Chat Window */
.chatwindow-wrapper .rce-container-mbox.whatsappme-container .rce-mbox{
background-color: darkseagreen;
box-shadow: 0px 1px 1px 1px darkseagreen;
}
.chatwindow-wrapper .rce-container-mbox .rce-mbox{
max-width: 400px;
}
.chatwindow-wrapper .rce-container-mbox.whatsappme-container .rce-mbox-right-notch{
fill: darkseagreen;
}
.chatwindow-wrapper .rce-mbox-time-block{ .chatwindow-wrapper .rce-mbox-time-block{
background: linear-gradient(0deg,#00000014,#0000); background: linear-gradient(0deg,#00000014,#0000);
color: #00000073; color: #00000073;

@ -4,6 +4,10 @@ export default {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: false, darkMode: false,
theme: { theme: {
colors: {
'whatsapp': '#25D366',
'whatsappme': '#1ba784',
},
extend: { extend: {
// gridTemplateColumns: { // gridTemplateColumns: {
// 'responsive':repeat(autofill,minmax('300px',1fr)) // 'responsive':repeat(autofill,minmax('300px',1fr))

Loading…
Cancel
Save