Chore: refactor embedded chatbot (#5125)

This commit is contained in:
KVOJJJin
2024-06-14 08:42:41 +08:00
committed by GitHub
parent 54e02b8147
commit 4289f17be2
15 changed files with 1051 additions and 20 deletions

View File

@@ -0,0 +1,46 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { memo } from 'react'
type InputProps = {
form: any
value: string
onChange: (variable: string, value: string) => void
}
const FormInput: FC<InputProps> = ({
form,
value,
onChange,
}) => {
const { t } = useTranslation()
const {
type,
label,
required,
max_length,
variable,
} = form
if (type === 'paragraph') {
return (
<textarea
value={value}
className='grow h-[104px] rounded-lg bg-gray-100 px-2.5 py-2 outline-none appearance-none resize-none'
onChange={e => onChange(variable, e.target.value)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}
return (
<input
className='grow h-9 rounded-lg bg-gray-100 px-2.5 outline-none appearance-none'
value={value || ''}
maxLength={max_length}
onChange={e => onChange(variable, e.target.value)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}
export default memo(FormInput)

View File

@@ -0,0 +1,83 @@
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useEmbeddedChatbotContext } from '../context'
import Input from './form-input'
import { PortalSelect } from '@/app/components/base/select'
const Form = () => {
const { t } = useTranslation()
const {
inputsForms,
newConversationInputs,
handleNewConversationInputsChange,
isMobile,
} = useEmbeddedChatbotContext()
const handleFormChange = useCallback((variable: string, value: string) => {
handleNewConversationInputsChange({
...newConversationInputs,
[variable]: value,
})
}, [newConversationInputs, handleNewConversationInputsChange])
const renderField = (form: any) => {
const {
label,
required,
variable,
options,
} = form
if (form.type === 'text-input' || form.type === 'paragraph') {
return (
<Input
form={form}
value={newConversationInputs[variable]}
onChange={handleFormChange}
/>
)
}
if (form.type === 'number') {
return (
<input
className="grow h-9 rounded-lg bg-gray-100 px-2.5 outline-none appearance-none"
type="number"
value={newConversationInputs[variable] || ''}
onChange={e => handleFormChange(variable, e.target.value)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}
return (
<PortalSelect
popupClassName='w-[200px]'
value={newConversationInputs[variable]}
items={options.map((option: string) => ({ value: option, name: option }))}
onSelect={item => handleFormChange(variable, item.value as string)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}
if (!inputsForms.length)
return null
return (
<div className='mb-4 py-2'>
{
inputsForms.map(form => (
<div
key={form.variable}
className={`flex mb-3 last-of-type:mb-0 text-sm text-gray-900 ${isMobile && '!flex-wrap'}`}
>
<div className={`shrink-0 mr-2 py-2 w-[128px] ${isMobile && '!w-full'}`}>{form.label}</div>
{renderField(form)}
</div>
))
}
</div>
)
}
export default Form

View File

@@ -0,0 +1,168 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { useEmbeddedChatbotContext } from '../context'
import Form from './form'
import Button from '@/app/components/base/button'
import AppIcon from '@/app/components/base/app-icon'
import { MessageDotsCircle } from '@/app/components/base/icons/src/vender/solid/communication'
import { Edit02 } from '@/app/components/base/icons/src/vender/line/general'
import { Star06 } from '@/app/components/base/icons/src/vender/solid/shapes'
import { FootLogo } from '@/app/components/share/chat/welcome/massive-component'
const ConfigPanel = () => {
const { t } = useTranslation()
const {
appData,
inputsForms,
handleStartChat,
showConfigPanelBeforeChat,
isMobile,
} = useEmbeddedChatbotContext()
const [collapsed, setCollapsed] = useState(true)
const customConfig = appData?.custom_config
const site = appData?.site
return (
<div className='flex flex-col max-h-[80%] w-full max-w-[720px]'>
<div
className={cn(
'grow rounded-xl overflow-y-auto',
showConfigPanelBeforeChat && 'border-[0.5px] border-gray-100 shadow-lg',
!showConfigPanelBeforeChat && collapsed && 'border border-indigo-100',
!showConfigPanelBeforeChat && !collapsed && 'border-[0.5px] border-gray-100 shadow-lg',
)}
>
<div
className={`
flex flex-wrap px-6 py-4 rounded-t-xl bg-indigo-25
${isMobile && '!px-4 !py-3'}
`}
>
{
showConfigPanelBeforeChat && (
<>
<div className='flex items-center h-8 text-2xl font-semibold text-gray-800'>
<AppIcon
icon={appData?.site.icon}
background='transparent'
size='small'
/>
{appData?.site.title}
</div>
{
appData?.site.description && (
<div className='mt-2 w-full text-sm text-gray-500'>
{appData?.site.description}
</div>
)
}
</>
)
}
{
!showConfigPanelBeforeChat && collapsed && (
<>
<Star06 className='mr-1 mt-1 w-4 h-4 text-indigo-600' />
<div className='grow py-[3px] text-[13px] text-indigo-600 leading-[18px] font-medium'>
{t('share.chat.configStatusDes')}
</div>
<Button
className='shrink-0 px-2 py-0 h-6 bg-white text-xs font-medium text-primary-600 rounded-md'
onClick={() => setCollapsed(false)}
>
<Edit02 className='mr-1 w-3 h-3' />
{t('common.operation.edit')}
</Button>
</>
)
}
{
!showConfigPanelBeforeChat && !collapsed && (
<>
<Star06 className='mr-1 mt-1 w-4 h-4 text-indigo-600' />
<div className='grow py-[3px] text-[13px] text-indigo-600 leading-[18px] font-medium'>
{t('share.chat.privatePromptConfigTitle')}
</div>
</>
)
}
</div>
{
!collapsed && !showConfigPanelBeforeChat && (
<div className='p-6 rounded-b-xl'>
<Form />
<div className={cn('pl-[136px] flex items-center', isMobile && '!pl-0')}>
<Button
type='primary'
className='mr-2 text-sm font-medium'
onClick={() => {
setCollapsed(true)
handleStartChat()
}}
>
{t('common.operation.save')}
</Button>
<Button
className='text-sm font-medium'
onClick={() => setCollapsed(true)}
>
{t('common.operation.cancel')}
</Button>
</div>
</div>
)
}
{
showConfigPanelBeforeChat && (
<div className='p-6 rounded-b-xl'>
<Form />
<Button
className={cn('px-4 py-0 h-9', inputsForms.length && !isMobile && 'ml-[136px]')}
type='primary'
onClick={handleStartChat}
>
<MessageDotsCircle className='mr-2 w-4 h-4 text-white' />
{t('share.chat.startChat')}
</Button>
</div>
)
}
</div>
{
showConfigPanelBeforeChat && (site || customConfig) && (
<div className='mt-4 flex flex-wrap justify-between items-center py-2 text-xs text-gray-400'>
{site?.privacy_policy
? <div className={cn(isMobile && 'mb-2 w-full text-center')}>{t('share.chat.privacyPolicyLeft')}
<a
className='text-gray-500 px-1'
href={site?.privacy_policy}
target='_blank' rel='noopener noreferrer'>{t('share.chat.privacyPolicyMiddle')}</a>
{t('share.chat.privacyPolicyRight')}
</div>
: <div>
</div>}
{
customConfig?.remove_webapp_brand
? null
: (
<div className={cn('flex items-center justify-end', isMobile && 'w-full')}>
<div className='flex items-center pr-3 space-x-3'>
<span className='uppercase'>{t('share.chat.powerBy')}</span>
{
customConfig?.replace_webapp_logo
? <img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
: <FootLogo />
}
</div>
</div>
)
}
</div>
)
}
</div>
)
}
export default ConfigPanel