模板消息等

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 = {
text: {
type: 'text',
contentToSend: (msg) => ({ type: 'text', from: '+8617607730395', to: '', text: { body: msg.text } }),
contentToRender: (msg) => ({...msg}),
contentToSend: (msg) => ({ renderId: msg.id, to: msg.to, msgtype: 'text', msgcontent: { body: msg.text } }),
contentToRender: (msg) => ({ ...msg }),
},
whatsappTemplate: {
type: 'template',
contentToSend: (msg) => ({
template: {
namespace: msg.whatsappTemplate.namespace,
language: msg.whatsappTemplate.language,
type: msg.whatsappTemplate.type,
components: msg.whatsappTemplate.components,
},
}),
contentToSend: (msg) => ({ renderId: msg.id, to: msg.to, msgtype: 'template', msgcontent: msg.template }),
contentToRender: (msg) => {
console.log(msg);
const templateDataMapped = msg.template?.components ? msg.template.components.reduce((r, v) => ({...r, [v.type]: v}), {}) : null;
const templateParam = (templateDataMapped?.body?.parameters || []).map(e => e.text);
const fillTemplate = templateParam.length ? replaceTemplateString(msg.template_origin.components.body?.[0]?.text || '', templateParam) : (msg.template_origin.components.body?.[0]?.text || '');
return {
...msg,
type: 'text',
title: msg.template_origin.components.header?.[0]?.text || '',
text: fillTemplate, // msg.template_origin.components.body?.[0]?.text || '',
};
},
},
};
export const whatsappMsgMapped = {
@ -23,23 +49,48 @@ export const whatsappMsgMapped = {
return result?.whatsappInboundMessage || null;
},
contentToRender: (result) => {
console.log( 'whatsapp.inbound_message.received', result);
console.log('whatsapp.inbound_message.received to render', result);
const contentObj = result?.whatsappInboundMessage || result; // debug:
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 = {
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: {
type: 'photo',
data: (msg) => ({
data: {
uri: msg.image.link,
width: 200,
height: 200,
alt: '',
},
data: { id: msg.wamid, uri: msg.image.link, width: 200, height: 200, alt: '' },
onOpen: () => {
console.log('Open image', msg.image.link);
},
@ -48,18 +99,14 @@ export const whatsappMsgTypeMapped = {
sticker: {
type: 'photo',
data: (msg) => ({
data: {
uri: msg.sticker.link,
width: 150,
height: 120,
alt: '',
},
data: { id: msg.wamid, uri: msg.sticker.link, width: 150, height: 120, alt: '' },
}),
},
video: {
type: 'video',
data: (msg) => ({
data: {
id: msg.wamid,
videoURL: msg.video.link,
status: {
click: true,
@ -72,13 +119,25 @@ export const whatsappMsgTypeMapped = {
audio: {
type: 'audio',
data: (msg) => ({
id: msg.wamid,
data: {
audioURL: msg.audio.link,
},
}),
},
'unsupported': { type: 'system', data: (msg) => ({ text: 'Message type is currently not supported.' }) },
// 'unsupported': { type: 'text', data: (msg) => ({ text: 'Message type is currently not supported.' }) }
unsupported: { type: 'system', 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',
// location: 'location',
// contact: 'contact',
@ -87,23 +146,24 @@ export const whatsappMsgTypeMapped = {
// 'contact-card-with-photo-and-label': 'contact-card-with-photo-and-label',
};
export const parseRenderMessageItem = (msg) => {
console.log(msg, '[[[[');
console.log('parseRenderMessageItem', msg);
return {
date: msg?.sendTime || '',
...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}),
id: msg.id,
...(whatsappMsgTypeMapped?.[msg.type]?.type(msg) || { type: 'text' }), // type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text',
sender: msg.from,
type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text',
status: msg?.status || 'waiting',
// title: msg.customerProfile.name,
date: msg.sendTime,
// replyButton: true,
};
};
export const parseRenderMessageList = (messages) => {
return messages.map((msg) => {
return {
...(whatsappMsgTypeMapped?.[msg.type]?.data(msg) || {}),
...(whatsappMsgTypeMapped?.[msg.type]?.type(msg) || { type: 'text' }), // type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text',
id: msg.id,
sender: msg.from,
type: whatsappMsgTypeMapped?.[msg.type]?.type || 'text',
// title: msg.customerProfile.name,
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 { whatsappMsgMapped, sentMsgTypeMapped, parseRenderMessageList } from '@/lib/msgUtils';
import { groupBy } from '@/utils/utils';
@ -13,48 +13,77 @@ export async function fetchJSON(url, data) {
ifp = params ? '?' : 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}`);
return await response.json();
}
// 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.157:8888/whatever/';
// const WS_URL = 'ws://202.103.68.144: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: WS_URL, protocol: 'WhatsApp' });
export const useConversations = () => {
const [errors, setErrors] = useState([]);
const [messages, setMessages] = useState([]); // active conversation
const [conversations, setConversations] = useState({}); // all conversation
const [activeConversations, setActiveConversations] = useState({}); // all active conversation
const [currentID, setCurrentID] = useState();
const [conversationsList, setConversationsList] = useState([]); // open conversations
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(() => {
getConversations();
getConversationsList();
// getTemplates();
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 dataMapped = data.reduce((r, v) => ({ ...r, [v.id]: [] }), {});
setConversationsList(data);
if (data && data.length) {
switchConversation(data[0]);
}
setActiveConversations({...dataMapped, ...activeConversations});
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 [templatesList, setTemplatesList] = useState([]);
const getTemplates = async () => {
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);
};
@ -66,36 +95,85 @@ export const useConversations = () => {
};
const switchConversation = (cc) => {
console.log('switch to ', cc.id, cc);
setCurrentID(cc.id);
setCurrentConversation(cc);
// debug: 0
const _all = all.map((ele) => whatsappMsgMapped['whatsapp.inbound_message.received'].contentToRender(ele));
setMessages([..._all,...conversations[cc.id] || []]);
const _all = [];
// const _all = all.map((ele) => whatsappMsgMapped['whatsapp.inbound_message.received'].contentToRender(ele)); // debug: 0
// 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
getCustomerProfile(cc.id);
// getCustomerProfile(cc.id);
};
useEffect(() => {
return () => {
getCustomerProfile(currentID);
};
}, [currentID]);
/**
* *****************************************************************************************************
* websocket --------------------------------------------------------------------------------------------
* *****************************************************************************************************
*/
const addError = (reason) => {
setErrors(prevErrors => [...prevErrors, { reason }]);
}
setErrors((prevErrors) => [...prevErrors, { reason }]);
};
const addMessageToConversations = (customerId, message) => {
setConversations((prevList) => ({
...prevList,
[customerId]: [...(prevList[customerId] || []), message],
}));
// setActiveConversations((prevList) => ({
// ...prevList,
// [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) => {
setMessages((prevMessages) => [ ...prevMessages, message]);
addMessageToConversations(currentConversation.id, message);
setMessages((prevMessages) => [...prevMessages, 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) => {
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;
if (!result) {
@ -104,293 +182,351 @@ export const useConversations = () => {
if (typeof result.type === 'string' && result.type === 'error') {
addError('Error Connecting to Server');
}
console.log(result, 'handleMessage------------------');
console.log(result.type, 'result.type');
const msgObj = whatsappMsgMapped[result.type].getMsg(result);
console.log(msgObj, 'msgObj');
addMessage(whatsappMsgMapped[result.type].contentToRender(msgObj));
const msgRender = 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 contentToSend = sentMsgTypeMapped[msgObj.type].contentToSend(msgObj);
realtimeAPI.sendMessage(contentToSend);
const contentToRender = sentMsgTypeMapped[msgObj.type].contentToRender(msgObj);
console.log(contentToRender, 'contentToRender sendMessage------------------');
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 {
errors, messages, conversationsList, currentConversation, sendMessage,
getConversations, switchConversation,
errors,
messages,
conversationsList,
currentConversation,
sendMessage,
getConversationsList,
switchConversation,
// templates: templatesList, // setTemplates, getTemplates,
templates, // debug: 0
customerOrderProfile,
};
}
// test: 0
};
// test: 0 "type": "whatsapp.inbound_message.received",
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',
'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==',
'id': '65b1de2f3f0bb66a91377930',
'wamid': 'wamid.HBgNODYxODc3NzM5Njk1MRUCABEYEkM4NTU5MjMyRDFCRkE5NjM2RAA=',
'status': 'sent',
'from': '+8617607730395',
'to': '+8618777396951',
'wabaId': '190290134156880',
'from': '+8613317835586',
'customerProfile': {
'name': 'qqs',
'type': 'template',
'template': {
'name': 'hello',
'language': {
'code': 'zh_CN',
},
},
'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,
'conversation': {
'id': 'ddb659076d0e970f16fb19520c116a1b',
'originType': 'marketing',
'expireTime': '2024-01-26T04:07:00.000Z',
},
'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',
'wamid': 'wamid.HBgNODYxMzMxNzgzNTU4NhUCABIYFDNBRjUxNzdCQ0FEOTlFQzc5MzQ1AA==',
'wabaId': '190290134156880',
'from': '+8613317835586',
'customerProfile': {
'name': 'qqs',
'id': 'evt_eEMtA0PkkyACiS5o',
'type': 'whatsapp.template.reviewed',
'apiVersion': 'v2',
'createTime': '2023-02-20T12:00:00.000Z',
'whatsappTemplate': {
'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 = [
{
"wabaId": "190290134156880",
"name": "say_hi",
"language": "en",
"components": [
'wabaId': '190290134156880',
'name': 'say_hi',
'language': 'en',
'components': [
{
"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_.",
"example": {
"body_text": [
[
"Mike",
"Jimmy"
]
]
}
'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_.",
'example': {
'body_text': [['Mike', 'Jimmy']],
},
},
{
"type": "BUTTONS",
"buttons": [
'type': 'BUTTONS',
'buttons': [
{
"type": "URL",
"text": "Asia Highlights",
"url": "https://www.asiahighlights.com/"
}
]
}
'type': 'URL',
'text': 'Asia Highlights',
'url': 'https://www.asiahighlights.com/',
},
],
},
],
"category": "UTILITY",
"status": "REJECTED",
"qualityRating": "UNKNOWN",
"reason": "INCORRECT_CATEGORY",
"createTime": "2024-01-23T02:26:33.012Z",
"updateTime": "2024-01-23T02:26:35.397Z",
"statusUpdateEvent": "REJECTED"
'category': 'UTILITY',
'status': 'REJECTED',
'qualityRating': 'UNKNOWN',
'reason': 'INCORRECT_CATEGORY',
'createTime': '2024-01-23T02:26:33.012Z',
'updateTime': '2024-01-23T02:26:35.397Z',
'statusUpdateEvent': 'REJECTED',
},
{
"wabaId": "190290134156880",
"name": "i_hope_this_message_finds_you_well",
"language": "en",
"components": [
'wabaId': '190290134156880',
'name': 'i_hope_this_message_finds_you_well',
'language': 'en',
'components': [
{
"type": "BODY",
"text": "Hi {{customer_name}}, I hope this message finds you well. Did you see my previous message?",
"example": {
"body_text": [
[
"Mike"
]
]
}
}
'type': 'BODY',
'text': 'Hi {{customer_name}}, I hope this message finds you well. Did you see my previous message?',
'example': {
'body_text': [['Mike']],
},
},
],
"category": "UTILITY",
"status": "REJECTED",
"qualityRating": "UNKNOWN",
"reason": "INCORRECT_CATEGORY",
"createTime": "2024-01-23T02:22:20.232Z",
"updateTime": "2024-01-23T02:22:22.937Z",
"statusUpdateEvent": "REJECTED"
'category': 'UTILITY',
'status': 'REJECTED',
'qualityRating': 'UNKNOWN',
'reason': 'INCORRECT_CATEGORY',
'createTime': '2024-01-23T02:22:20.232Z',
'updateTime': '2024-01-23T02:22:22.937Z',
'statusUpdateEvent': 'REJECTED',
},
{
"wabaId": "190290134156880",
"name": "asia_highlights_has_receive_your_inquiry",
"language": "en",
"components": [
'wabaId': '190290134156880',
'name': 'asia_highlights_has_receive_your_inquiry',
'language': 'en',
'components': [
{
"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.",
"example": {
"body_text": [
[
"Jimmy Liow"
]
]
}
'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.',
'example': {
'body_text': [['Jimmy Liow']],
},
},
{
"type": "HEADER",
"format": "TEXT",
"text": "Asia highlights has receive your inquiry"
'type': 'HEADER',
'format': 'TEXT',
'text': 'Asia highlights has receive your inquiry',
},
{
"type": "FOOTER",
"text": "Kind regards, Asia Highlights Team"
'type': 'FOOTER',
'text': 'Kind regards, Asia Highlights Team',
},
{
"type": "BUTTONS",
"buttons": [
'type': 'BUTTONS',
'buttons': [
{
"type": "URL",
"text": "Asia Highlights",
"url": "https://www.asiahighlights.com/"
}
]
}
'type': 'URL',
'text': 'Asia Highlights',
'url': 'https://www.asiahighlights.com/',
},
],
},
],
"category": "UTILITY",
"status": "APPROVED",
"qualityRating": "UNKNOWN",
"reason": "NONE",
"createTime": "2024-01-19T05:59:32.933Z",
"updateTime": "2024-01-19T05:59:55.581Z",
"statusUpdateEvent": "APPROVED"
'category': 'UTILITY',
'status': 'APPROVED',
'qualityRating': 'UNKNOWN',
'reason': 'NONE',
'createTime': '2024-01-19T05:59:32.933Z',
'updateTime': '2024-01-19T05:59:55.581Z',
'statusUpdateEvent': 'APPROVED',
},
{
"wabaId": "190290134156880",
"name": "hello",
"language": "zh_CN",
"components": [
'wabaId': '190290134156880',
'name': 'hello',
'language': 'zh_CN',
'components': [
{
"type": "BODY",
"text": "你好,这是一个测试程序"
'type': 'BODY',
'text': '你好,这是一个测试程序',
},
{
"type": "HEADER",
"format": "TEXT",
"text": "Hello 同学"
'type': 'HEADER',
'format': 'TEXT',
'text': 'Hello 同学',
},
{
"type": "FOOTER",
"text": "Global Highlights"
'type': 'FOOTER',
'text': 'Global Highlights',
},
{
"type": "BUTTONS",
"buttons": [
'type': 'BUTTONS',
'buttons': [
{
"type": "URL",
"text": "about us",
"url": "https://www.globalhighlights.com/"
}
]
}
'type': 'URL',
'text': 'about us',
'url': 'https://www.globalhighlights.com/',
},
],
},
],
"category": "MARKETING",
"status": "APPROVED",
"qualityRating": "UNKNOWN",
"reason": "NONE",
"createTime": "2023-11-17T03:26:10.961Z",
"updateTime": "2023-11-17T13:36:33.623Z",
"statusUpdateEvent": "APPROVED"
}
'category': 'MARKETING',
'status': 'APPROVED',
'qualityRating': 'UNKNOWN',
'reason': 'NONE',
'createTime': '2023-11-17T03:26:10.961Z',
'updateTime': '2023-11-17T13:36:33.623Z',
'statusUpdateEvent': 'APPROVED',
},
];

@ -4,11 +4,12 @@ import { Input, Button, Tabs, List, Space, Popover, Flex } from 'antd';
// import { Input } from 'react-chat-elements';
import { useConversationContext } from '@/stores/ConversationContext';
import { LikeOutlined, MessageOutlined, StarOutlined, SendOutlined, PlusOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { isEmpty } from '@/utils/utils';
const InputBox = observer(({ onSend }) => {
const { templates } = useConversationContext();
const { currentConversation, templates } = useConversationContext();
const [textContent, setTextContent] = useState('');
console.log( 'ttt');
console.log('ttt');
const handleSendText = () => {
// console.log(textContent);
@ -17,39 +18,82 @@ const InputBox = observer(({ onSend }) => {
type: 'text',
text: textContent,
sender: 'me',
from: '',
// from: '',
to: currentConversation.channel_id,
id: Date.now().toString(16),
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);
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 handleOpenChange = (newOpen) => {
setOpenTemplates(newOpen);
};
const onSendTemplates = () => {
setOpenTemplates(false);
// todo: send
}
return (
<div>
<Flex gap={8}>
<Popover
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'
dataSource={templates}
renderItem={(item, index) => (
<List.Item
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
</Button>,
]}>

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

@ -19,6 +19,17 @@
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{
background: linear-gradient(0deg,#00000014,#0000);
color: #00000073;

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

Loading…
Cancel
Save