perf: 模板: 有header, footer, buttons

dev/chat
Lei OT 1 year ago
parent acd0c198fc
commit d242ed3909

@ -161,7 +161,7 @@ export const sentMsgTypeMapped = {
actionId: msg.id,
conversationid: msg.id.split('.')[0],
type: 'text',
title: msg.template_origin.components.header?.[0]?.text || '',
title: msg.template.name, // || msg.template_origin.components.header?.[0]?.text || '',
text: autoLinkText(templateDataMapped?.body?.text || ''), // msg.template_origin.components.body?.[0]?.text || '',
};
},

@ -3,8 +3,9 @@ import { App, Popover, Flex, Button, List, Input } from 'antd';
import { MessageOutlined, SendOutlined } from '@ant-design/icons';
import useAuthStore from '@/stores/AuthStore';
import useConversationStore from '@/stores/ConversationStore';
import { cloneDeep, getNestedValue, objectMapper } from '@/utils/utils';
import { cloneDeep, getNestedValue, objectMapper, sortArrayByOrder } from '@/utils/utils';
import { replaceTemplateString } from '@/lib/msgUtils';
import { isEmpty } from '@/utils/commons';
const splitTemplate = (template) => {
const placeholders = template.match(/{{(.*?)}}/g) || [];
@ -40,7 +41,7 @@ const InputTemplate = ({ mobile, disabled = false, invokeSendMessage }) => {
const handleSearchTemplates = (val) => {
if (val.toLowerCase().trim() !== '') {
const res = templates.filter(
(item) => item.name.includes(val.toLowerCase().trim()) || item.components_origin.some((itemc) => itemc.text.toLowerCase().includes(val.toLowerCase().trim()))
(item) => item.name.includes(val.toLowerCase().trim()) || item.components_origin.some((itemc) => (itemc?.text || '').toLowerCase().includes(val.toLowerCase().trim()))
);
setDataSource(res);
return false;
@ -57,18 +58,18 @@ const InputTemplate = ({ mobile, disabled = false, invokeSendMessage }) => {
template: {
name: fromTemplate.name,
language: { code: fromTemplate.language },
components: fromTemplate.components_origin.map((citem) => {
components: sortArrayByOrder(fromTemplate.components_origin.map((citem) => {
const keys = ((citem?.text || '').match(/{{(.*?)}}/g) || []).map((key) => key.replace(/{{|}}/g, ''));
const params = keys.map((v) => ({ type: 'text', text: getNestedValue(mergeInput, [v]) }));
const paramText = params.map((p) => p.text);
const fillTemplate = paramText.length ? replaceTemplateString(citem?.text || '', paramText) : citem?.text || '';
valid = keys.length !== paramText.filter((s) => s).length ? false : valid;
return {
return citem.type.toLowerCase() === 'body' ? {
type: citem.type.toLowerCase(),
parameters: params,
text: fillTemplate,
};
}),
} : {...citem, type: citem.type.toLowerCase(),};
}), 'type', ['header', 'body', 'footer', 'buttons'] ),
},
template_origin: fromTemplate,
};
@ -78,6 +79,7 @@ const InputTemplate = ({ mobile, disabled = false, invokeSendMessage }) => {
description: '信息未填写完整, 请补充填写',
placement: 'top',
duration: 3,
closeIcon: false,
});
return false;
}
@ -92,6 +94,49 @@ const InputTemplate = ({ mobile, disabled = false, invokeSendMessage }) => {
});
};
const renderHeader = ({ tempItem }) => {
if (isEmpty(tempItem.components.header)) {
return null;
}
const headerObj = tempItem.components.header[0];
return (
<div className='pb-1'>
{'text' === headerObj.format.toLowerCase() && <div>{headerObj.text}</div>}
{'image' === headerObj.format.toLowerCase() && <img src={headerObj.example.header_url} height={100}></img>}
{['document', 'video'].includes(headerObj.format.toLowerCase()) && (
<a href={headerObj.example.header_url} target='_blank' key={headerObj.format} rel='noreferrer' className='text-sm'>
[&nbsp;{headerObj.format}&nbsp;]({headerObj.example.header_url})
</a>
)}
</div>
);
}
const renderButtons = ({ tempItem }) => {
if (isEmpty(tempItem.components.buttons)) {
return null;
}
const buttons = tempItem.components.buttons.reduce((r, c) => r.concat(c.buttons), []);
return (
<div className='flex gap-1 pt-1'>
{buttons.map((btn, index) =>
btn.type.toLowerCase() === 'url' ? (
<Button className='text-blue-500' size={'small'} href={btn.url} target={'_blank'} key={btn.url} rel='noreferrer'>
{btn.text}
</Button>
) : btn.type.toLowerCase() === 'phone_number' ? (
<Button className='text-blue-500' size={'small'} key={btn.phone_number} rel='noreferrer'>
{btn.text} ({btn.phone_number})
</Button>
) : (
<Button className='text-blue-500' size={'small'} key={btn.type} rel='noreferrer'>
{btn.text}
</Button>
)
)}
</div>
);
}
const renderForm = ({ tempItem }) => {
const templateText = tempItem.components.body?.[0]?.text || '';
const tempArr = splitTemplate(templateText);
@ -167,8 +212,10 @@ const InputTemplate = ({ mobile, disabled = false, invokeSendMessage }) => {
description={
<>
<div className=' max-h-32 overflow-y-auto divide-dashed divide-x-0 divide-y divide-gray-300'>
<div className='text-slate-500'>{renderForm({ tempItem: item })}</div>
{renderHeader({ tempItem: item })}
<div className='text-slate-500 py-1'>{renderForm({ tempItem: item })}</div>
{item.components?.footer?.[0] ? <div className=''>{item.components.footer[0].text || ''}</div> : null}
{renderButtons({ tempItem: item })}
</div>
</>
}

@ -4,7 +4,7 @@ import { App, Button } from 'antd';
import { DownOutlined, LoadingOutlined } from '@ant-design/icons';
import { useShallow } from 'zustand/react/shallow';
import useConversationStore from '@/stores/ConversationStore';
import { isEmpty, } from '@/utils/utils';
import { groupBy, isEmpty, } from '@/utils/utils';
const MessagesList = ({ messages, handlePreview, reference, longListLoading, getMoreMessages, shouldScrollBottom, loadNextPage, handleContactClick, ...props }) => {
@ -32,7 +32,16 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
useEffect(scrollToBottom, [messages]);
const RenderText = memo(function renderText({ str, className }) {
const RenderText = memo(function renderText({ str, className, template }) {
let headerObj, footerObj, buttonsArr;
if (!isEmpty(template)) {
const componentsObj = groupBy(template.components, (item) => item.type);
headerObj = componentsObj.header[0];
footerObj = componentsObj.footer[0];
buttonsArr = componentsObj.buttons.reduce((r, c) => r.concat(c.buttons), []);
}
const parts = str.split(/(https?:\/\/[^\s]+|\p{Emoji_Presentation})/gmu).filter((s) => s !== '');
const links = str.match(/https?:\/\/[\S]+/gi) || [];
const emojis = str.match(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g) || [];
@ -49,6 +58,17 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
}, []);
return (
<span className={`text-sm leading-5 emoji-text whitespace-pre-wrap ${className} ${extraClass}`} key={'msg-text'}>
{headerObj ? (
<div className='text-neutral-500 text-center'>
{'text' === headerObj.format.toLowerCase() && <div>{headerObj.text}</div>}
{'image' === headerObj.format.toLowerCase() && <img src={headerObj.example.header_url} height={100}></img>}
{['document', 'video'].includes(headerObj.format.toLowerCase()) && (
<a href={headerObj.example.header_url} target='_blank' key={headerObj.format} rel='noreferrer' className='text-sm'>
[&nbsp;{headerObj.format}&nbsp;]
</a>
)}
</div>
) : null}
{(objArr || []).map((part, index) => {
if (part.type === 'link') {
return (
@ -61,6 +81,26 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
return part.key;
}
})}
{footerObj ? <div className=' text-neutral-500'>{footerObj.text}</div> : null}
{buttonsArr && buttonsArr.length > 0 ? (
<div className='flex flex-row gap-1'>
{buttonsArr.map((btn, index) =>
btn.type.toLowerCase() === 'url' ? (
<Button className='text-blue-500' size={'small'} href={btn.url} target={'_blank'} key={btn.url} rel='noreferrer'>
{btn.text}
</Button>
) : btn.type.toLowerCase() === 'phone_number' ? (
<Button className='text-blue-500' size={'small'} key={btn.phone_number} rel='noreferrer'>
{btn.text} ({btn.phone_number})
</Button>
) : (
<Button className='text-blue-500' size={'small'} key={btn.type}>
{btn.text}
</Button>
)
)}
</div>
) : null}
</span>
);
});
@ -99,7 +139,7 @@ const MessagesList = ({ messages, handlePreview, reference, longListLoading, get
onReplyMessageClick={() => scrollToMessage(message.reply.id)}
onOpen={() => handlePreview(message)}
onTitleClick={() => handlePreview(message)}
text={<RenderText str={message?.text || ''} className={message.status === 'failed' ? 'line-through text-neutral-400' : ''} />}
text={<RenderText str={message?.text || ''} className={message.status === 'failed' ? 'line-through text-neutral-400' : ''} template={message.template} />}
{...(message.sender === 'me'
? {
styles: { backgroundColor: '#ccd4ae' },

Loading…
Cancel
Save