Spaces:
Running
Running
'use client' | |
import type { FC } from 'react' | |
import React, { useEffect, useState } from 'react' | |
import { useTranslation } from 'react-i18next' | |
import { useContext } from 'use-context-selector' | |
import TemplateVarPanel, { PanelTitle, VarOpBtnGroup } from '../value-panel' | |
import s from './style.module.css' | |
import { AppInfo, ChatBtn, EditBtn, FootLogo, PromptTemplate } from './massive-component' | |
import type { SiteInfo } from '@/models/share' | |
import type { PromptConfig } from '@/models/debug' | |
import { ToastContext } from '@/app/components/base/toast' | |
import Select from '@/app/components/base/select' | |
import { DEFAULT_VALUE_MAX_LEN } from '@/config' | |
// regex to match the {{}} and replace it with a span | |
const regex = /\{\{([^}]+)\}\}/g | |
export type IWelcomeProps = { | |
// conversationName: string | |
hasSetInputs: boolean | |
isPublicVersion: boolean | |
siteInfo: SiteInfo | |
promptConfig: PromptConfig | |
onStartChat: (inputs: Record<string, any>) => void | |
canEditInputs: boolean | |
savedInputs: Record<string, any> | |
onInputsChange: (inputs: Record<string, any>) => void | |
plan: string | |
canReplaceLogo?: boolean | |
customConfig?: { | |
remove_webapp_brand?: boolean | |
replace_webapp_logo?: string | |
} | |
} | |
const Welcome: FC<IWelcomeProps> = ({ | |
// conversationName, | |
hasSetInputs, | |
isPublicVersion, | |
siteInfo, | |
promptConfig, | |
onStartChat, | |
canEditInputs, | |
savedInputs, | |
onInputsChange, | |
customConfig, | |
}) => { | |
const { t } = useTranslation() | |
const hasVar = promptConfig.prompt_variables.length > 0 | |
const [isFold, setIsFold] = useState<boolean>(true) | |
const [inputs, setInputs] = useState<Record<string, any>>((() => { | |
if (hasSetInputs) | |
return savedInputs | |
const res: Record<string, any> = {} | |
if (promptConfig) { | |
promptConfig.prompt_variables.forEach((item) => { | |
res[item.key] = '' | |
}) | |
} | |
// debugger | |
return res | |
})()) | |
useEffect(() => { | |
if (!savedInputs) { | |
const res: Record<string, any> = {} | |
if (promptConfig) { | |
promptConfig.prompt_variables.forEach((item) => { | |
res[item.key] = '' | |
}) | |
} | |
setInputs(res) | |
} | |
else { | |
setInputs(savedInputs) | |
} | |
}, [savedInputs]) | |
const highLightPromoptTemplate = (() => { | |
if (!promptConfig) | |
return '' | |
const res = promptConfig.prompt_template.replace(regex, (match, p1) => { | |
return `<span class='text-gray-800 font-bold'>${inputs?.[p1] ? inputs?.[p1] : match}</span>` | |
}) | |
return res | |
})() | |
const { notify } = useContext(ToastContext) | |
const logError = (message: string) => { | |
notify({ type: 'error', message, duration: 3000 }) | |
} | |
// const renderHeader = () => { | |
// return ( | |
// <div className='absolute top-0 left-0 right-0 flex items-center justify-between border-b border-gray-100 mobile:h-12 tablet:h-16 px-8 bg-white'> | |
// <div className='text-gray-900'>{conversationName}</div> | |
// </div> | |
// ) | |
// } | |
const renderInputs = () => { | |
return ( | |
<div className='space-y-3'> | |
{promptConfig.prompt_variables.map(item => ( | |
<div className='tablet:flex items-start mobile:space-y-2 tablet:space-y-0 mobile:text-xs tablet:text-sm' key={item.key}> | |
<label className={`flex-shrink-0 flex items-center tablet:leading-9 mobile:text-gray-700 tablet:text-gray-900 mobile:font-medium pc:font-normal ${s.formLabel}`}>{item.name}</label> | |
{item.type === 'select' | |
&& ( | |
<Select | |
className='w-full' | |
defaultValue={inputs?.[item.key]} | |
onSelect={(i) => { setInputs({ ...inputs, [item.key]: i.value }) }} | |
items={(item.options || []).map(i => ({ name: i, value: i }))} | |
allowSearch={false} | |
bgClassName='bg-gray-50' | |
/> | |
) | |
} | |
{item.type === 'string' && ( | |
<input | |
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`} | |
value={inputs?.[item.key] || ''} | |
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }} | |
className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'} | |
maxLength={item.max_length || DEFAULT_VALUE_MAX_LEN} | |
/> | |
)} | |
{item.type === 'paragraph' && ( | |
<textarea | |
className="w-full h-[104px] flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50" | |
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`} | |
value={inputs?.[item.key] || ''} | |
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }} | |
/> | |
)} | |
{item.type === 'number' && ( | |
<input | |
type='number' | |
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`} | |
value={inputs?.[item.key] || ''} | |
onChange={(e) => { setInputs({ ...inputs, [item.key]: e.target.value }) }} | |
className={'w-full flex-grow py-2 pl-3 pr-3 box-border rounded-lg bg-gray-50'} | |
/> | |
)} | |
</div> | |
))} | |
</div> | |
) | |
} | |
const canChat = () => { | |
const prompt_variables = promptConfig?.prompt_variables | |
if (!inputs || !prompt_variables || prompt_variables?.length === 0) | |
return true | |
let hasEmptyInput = '' | |
const requiredVars = prompt_variables?.filter(({ key, name, required }) => { | |
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) | |
return res | |
}) || [] // compatible with old version | |
requiredVars.forEach(({ key, name }) => { | |
if (hasEmptyInput) | |
return | |
if (!inputs?.[key]) | |
hasEmptyInput = name | |
}) | |
if (hasEmptyInput) { | |
logError(t('appDebug.errorMessage.valueOfVarRequired', { key: hasEmptyInput })) | |
return false | |
} | |
return !hasEmptyInput | |
} | |
const handleChat = () => { | |
if (!canChat()) | |
return | |
onStartChat(inputs) | |
} | |
const renderNoVarPanel = () => { | |
if (isPublicVersion) { | |
return ( | |
<div> | |
<AppInfo siteInfo={siteInfo} /> | |
<TemplateVarPanel | |
isFold={false} | |
header={ | |
<> | |
<PanelTitle | |
title={t('share.chat.publicPromptConfigTitle')} | |
className='mb-1' | |
/> | |
<PromptTemplate html={highLightPromoptTemplate} /> | |
</> | |
} | |
> | |
<ChatBtn onClick={handleChat} /> | |
</TemplateVarPanel> | |
</div> | |
) | |
} | |
// private version | |
return ( | |
<TemplateVarPanel | |
isFold={false} | |
header={ | |
<AppInfo siteInfo={siteInfo} /> | |
} | |
> | |
<ChatBtn onClick={handleChat} /> | |
</TemplateVarPanel> | |
) | |
} | |
const renderVarPanel = () => { | |
return ( | |
<TemplateVarPanel | |
isFold={false} | |
header={ | |
<AppInfo siteInfo={siteInfo} /> | |
} | |
> | |
{renderInputs()} | |
<ChatBtn | |
className='mt-3 mobile:ml-0 tablet:ml-[128px]' | |
onClick={handleChat} | |
/> | |
</TemplateVarPanel> | |
) | |
} | |
const renderVarOpBtnGroup = () => { | |
return ( | |
<VarOpBtnGroup | |
onConfirm={() => { | |
if (!canChat()) | |
return | |
onInputsChange(inputs) | |
setIsFold(true) | |
}} | |
onCancel={() => { | |
setInputs(savedInputs) | |
setIsFold(true) | |
}} | |
/> | |
) | |
} | |
const renderHasSetInputsPublic = () => { | |
if (!canEditInputs) { | |
return ( | |
<TemplateVarPanel | |
isFold={false} | |
header={ | |
<> | |
<PanelTitle | |
title={t('share.chat.publicPromptConfigTitle')} | |
className='mb-1' | |
/> | |
<PromptTemplate html={highLightPromoptTemplate} /> | |
</> | |
} | |
/> | |
) | |
} | |
return ( | |
<TemplateVarPanel | |
isFold={isFold} | |
header={ | |
<> | |
<PanelTitle | |
title={t('share.chat.publicPromptConfigTitle')} | |
className='mb-1' | |
/> | |
<PromptTemplate html={highLightPromoptTemplate} /> | |
{isFold && ( | |
<div className='flex items-center justify-between mt-3 border-t border-indigo-100 pt-4 text-xs text-indigo-600'> | |
<span className='text-gray-700'>{t('share.chat.configStatusDes')}</span> | |
<EditBtn onClick={() => setIsFold(false)} /> | |
</div> | |
)} | |
</> | |
} | |
> | |
{renderInputs()} | |
{renderVarOpBtnGroup()} | |
</TemplateVarPanel> | |
) | |
} | |
const renderHasSetInputsPrivate = () => { | |
if (!canEditInputs || !hasVar) | |
return null | |
return ( | |
<TemplateVarPanel | |
isFold={isFold} | |
header={ | |
<div className='flex items-center justify-between text-indigo-600'> | |
<PanelTitle | |
title={!isFold ? t('share.chat.privatePromptConfigTitle') : t('share.chat.configStatusDes')} | |
/> | |
{isFold && ( | |
<EditBtn onClick={() => setIsFold(false)} /> | |
)} | |
</div> | |
} | |
> | |
{renderInputs()} | |
{renderVarOpBtnGroup()} | |
</TemplateVarPanel> | |
) | |
} | |
const renderHasSetInputs = () => { | |
if ((!isPublicVersion && !canEditInputs) || !hasVar) | |
return null | |
return ( | |
<div | |
className='pt-[88px] mb-5' | |
> | |
{isPublicVersion ? renderHasSetInputsPublic() : renderHasSetInputsPrivate()} | |
</div>) | |
} | |
return ( | |
<div className='relative tablet:min-h-[64px]'> | |
{/* {hasSetInputs && renderHeader()} */} | |
<div className='mx-auto pc:w-[794px] max-w-full mobile:w-full px-3.5'> | |
{/* Has't set inputs */} | |
{ | |
!hasSetInputs && ( | |
<div className='mobile:pt-[72px] tablet:pt-[128px] pc:pt-[200px]'> | |
{hasVar | |
? ( | |
renderVarPanel() | |
) | |
: ( | |
renderNoVarPanel() | |
)} | |
</div> | |
) | |
} | |
{/* Has set inputs */} | |
{hasSetInputs && renderHasSetInputs()} | |
{/* foot */} | |
{!hasSetInputs && ( | |
<div className='mt-4 flex justify-between items-center h-8 text-xs text-gray-400'> | |
{siteInfo.privacy_policy | |
? <div>{t('share.chat.privacyPolicyLeft')} | |
<a | |
className='text-gray-500 px-1' | |
href={siteInfo.privacy_policy} | |
target='_blank' rel='noopener noreferrer'>{t('share.chat.privacyPolicyMiddle')}</a> | |
{t('share.chat.privacyPolicyRight')} | |
</div> | |
: <div> | |
</div>} | |
{ | |
customConfig?.remove_webapp_brand | |
? null | |
: ( | |
<a className='flex items-center pr-3 space-x-3' href="https://dify.ai/" target="_blank"> | |
<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 /> | |
} | |
</a> | |
) | |
} | |
</div> | |
)} | |
</div> | |
</div > | |
) | |
} | |
export default React.memo(Welcome) | |