|
|
@ -1,23 +1,40 @@
|
|
|
|
import { useState } from 'react';
|
|
|
|
import { useState } from 'react';
|
|
|
|
import { Popover, Flex, Button, List } from 'antd';
|
|
|
|
import { App, Popover, Flex, Button, List, Input } from 'antd';
|
|
|
|
import { MessageOutlined, SendOutlined } from '@ant-design/icons';
|
|
|
|
import { MessageOutlined, SendOutlined } from '@ant-design/icons';
|
|
|
|
import { useAuthContext } from '@/stores/AuthContext';
|
|
|
|
import { useAuthContext } from '@/stores/AuthContext';
|
|
|
|
import { useConversationState } from '@/stores/ConversationContext';
|
|
|
|
import { useConversationState } from '@/stores/ConversationContext';
|
|
|
|
import { cloneDeep, getNestedValue, objectMapper } from '@/utils/utils';
|
|
|
|
import { cloneDeep, flush, getNestedValue, objectMapper } from '@/utils/utils';
|
|
|
|
import { v4 as uuid } from 'uuid';
|
|
|
|
import { v4 as uuid } from 'uuid';
|
|
|
|
import { whatsappTemplatesParamMapped, replaceTemplateString } from '@/lib/msgUtils';
|
|
|
|
import { replaceTemplateString } from '@/lib/msgUtils';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const splitTemplate = (template) => {
|
|
|
|
|
|
|
|
const placeholders = template.match(/{{(.*?)}}/g) || [];
|
|
|
|
|
|
|
|
const keys = placeholders.map((placeholder) => placeholder.slice(2, -2));
|
|
|
|
|
|
|
|
const arr = template.split(/{{(.*?)}}/).filter(Boolean);
|
|
|
|
|
|
|
|
const obj = arr.reduce((prev, curr, index) => {
|
|
|
|
|
|
|
|
if (keys.includes(curr)) {
|
|
|
|
|
|
|
|
prev.push({ key: curr });
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
prev.push(curr);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return prev;
|
|
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
|
|
|
};
|
|
|
|
const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
|
|
|
|
const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
|
|
|
|
|
|
|
|
const { notification } = App.useApp()
|
|
|
|
const { loginUser } = useAuthContext();
|
|
|
|
const { loginUser } = useAuthContext();
|
|
|
|
const { currentConversation, templates } = useConversationState();
|
|
|
|
const { currentConversation, templates } = useConversationState();
|
|
|
|
|
|
|
|
// 用于替换变量: customer, agent
|
|
|
|
|
|
|
|
const valueMapped = { ...cloneDeep(currentConversation), ...objectMapper(loginUser, { username: [{ key: 'agent_name' }, { key: 'your_name' }] }) };
|
|
|
|
|
|
|
|
|
|
|
|
const [openTemplates, setOpenTemplates] = useState(false);
|
|
|
|
const [openTemplates, setOpenTemplates] = useState(false);
|
|
|
|
const handleOpenChange = (newOpen) => {
|
|
|
|
const handleOpenChange = (newOpen) => {
|
|
|
|
setOpenTemplates(newOpen);
|
|
|
|
setOpenTemplates(newOpen);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
const handleSendTemplate = (fromTemplate) => {
|
|
|
|
const handleSendTemplate = (fromTemplate) => {
|
|
|
|
// 替换变量: customer, agent
|
|
|
|
const mergeInput = {...cloneDeep(valueMapped), ...activeInput[fromTemplate.name]};
|
|
|
|
const _conversation = { ...cloneDeep(currentConversation), ...objectMapper(loginUser, { username: { key: 'agent_name' } }) };
|
|
|
|
let valid = true;
|
|
|
|
const msgObj = {
|
|
|
|
const msgObj = {
|
|
|
|
type: 'whatsappTemplate',
|
|
|
|
type: 'whatsappTemplate',
|
|
|
|
to: currentConversation.whatsapp_phone_number,
|
|
|
|
to: currentConversation.whatsapp_phone_number,
|
|
|
@ -30,10 +47,11 @@ const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
|
|
|
|
name: fromTemplate.name,
|
|
|
|
name: fromTemplate.name,
|
|
|
|
language: { code: fromTemplate.language },
|
|
|
|
language: { code: fromTemplate.language },
|
|
|
|
components: fromTemplate.components_origin.map((citem) => {
|
|
|
|
components: fromTemplate.components_origin.map((citem) => {
|
|
|
|
const params = whatsappTemplatesParamMapped[fromTemplate.name].map((v) => ({ type: 'text', text: getNestedValue(_conversation, v) || '' }));
|
|
|
|
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 paramText = params.map((p) => p.text);
|
|
|
|
const fillTemplate = paramText.length ? replaceTemplateString(citem?.text || '', paramText) : citem?.text || '';
|
|
|
|
const fillTemplate = paramText.length ? replaceTemplateString(citem?.text || '', paramText) : citem?.text || '';
|
|
|
|
|
|
|
|
valid = keys.length !== flush(paramText).length ? false : valid;
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
type: citem.type.toLowerCase(),
|
|
|
|
type: citem.type.toLowerCase(),
|
|
|
|
parameters: params,
|
|
|
|
parameters: params,
|
|
|
@ -43,10 +61,49 @@ const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
|
|
|
|
},
|
|
|
|
},
|
|
|
|
template_origin: fromTemplate,
|
|
|
|
template_origin: fromTemplate,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
if (valid !== true) {
|
|
|
|
|
|
|
|
notification.warning({
|
|
|
|
|
|
|
|
message: '提示',
|
|
|
|
|
|
|
|
description: '信息未填写完整, 请补充填写',
|
|
|
|
|
|
|
|
placement: 'top',
|
|
|
|
|
|
|
|
duration: 3,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
}
|
|
|
|
invokeSendMessage(msgObj);
|
|
|
|
invokeSendMessage(msgObj);
|
|
|
|
setOpenTemplates(false);
|
|
|
|
setOpenTemplates(false);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [activeInput, setActiveInput] = useState({});
|
|
|
|
|
|
|
|
const onInput = (tempItem, key, val, initObj) => {
|
|
|
|
|
|
|
|
setActiveInput((prev) => {
|
|
|
|
|
|
|
|
return { ...prev, [tempItem.name]: { ...initObj, ...prev[tempItem.name], [key]: val } };
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderForm = (tempItem) => {
|
|
|
|
|
|
|
|
const templateText = tempItem.components.body?.[0]?.text || '';
|
|
|
|
|
|
|
|
const tempArr = splitTemplate(templateText);
|
|
|
|
|
|
|
|
const keys = templateText.match(/{{(.*?)}}/g).map((key) => key.replace(/{{|}}/g, ''));
|
|
|
|
|
|
|
|
const paramsVal = keys.reduce((r, k) => ({ ...r, [k]: getNestedValue(valueMapped, [k]) }), {});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return tempArr.map((ele) =>
|
|
|
|
|
|
|
|
typeof ele === 'string' ? (
|
|
|
|
|
|
|
|
<span key={ele.trim()} className=' text-wrap'>
|
|
|
|
|
|
|
|
{ele}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
) : (
|
|
|
|
|
|
|
|
<Input
|
|
|
|
|
|
|
|
key={ele.key}
|
|
|
|
|
|
|
|
onChange={(e) => onInput(tempItem, ele.key, e.target.value, paramsVal)}
|
|
|
|
|
|
|
|
className='w-auto max-w-24'
|
|
|
|
|
|
|
|
size={'small'}
|
|
|
|
|
|
|
|
title={ele.key}
|
|
|
|
|
|
|
|
placeholder={paramsVal[ele.key] || ele.key}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<>
|
|
|
|
<Popover
|
|
|
|
<Popover
|
|
|
@ -58,7 +115,7 @@ const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
|
|
|
|
renderItem={(item, index) => (
|
|
|
|
renderItem={(item, index) => (
|
|
|
|
<List.Item>
|
|
|
|
<List.Item>
|
|
|
|
<List.Item.Meta
|
|
|
|
<List.Item.Meta
|
|
|
|
className=' text-neutral-800'
|
|
|
|
className=' '
|
|
|
|
title={
|
|
|
|
title={
|
|
|
|
<Flex justify={'space-between'}>
|
|
|
|
<Flex justify={'space-between'}>
|
|
|
|
<>{item.components.header?.[0]?.text || item.name}</>
|
|
|
|
<>{item.components.header?.[0]?.text || item.name}</>
|
|
|
@ -67,7 +124,14 @@ const InputTemplate = ({ disabled = false, invokeSendMessage }) => {
|
|
|
|
</Button>
|
|
|
|
</Button>
|
|
|
|
</Flex>
|
|
|
|
</Flex>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
description={item.components.body?.[0]?.text}
|
|
|
|
description={
|
|
|
|
|
|
|
|
<>
|
|
|
|
|
|
|
|
<div className='divide-dashed divide-x-0 divide-y divide-gray-300'>
|
|
|
|
|
|
|
|
<div className='text-slate-500'>{renderForm(item)}</div>
|
|
|
|
|
|
|
|
{item.components?.footer?.[0] ? <div className=''>{item.components.footer[0].text || ''}</div> : null}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
</List.Item>
|
|
|
|
</List.Item>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|