mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 19:59:50 +08:00
23e3413655
Co-authored-by: StyleZhang <jasonapring2015@outlook.com>
357 lines
10 KiB
TypeScript
357 lines
10 KiB
TypeScript
'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
|
|
canEidtInpus: boolean
|
|
savedInputs: Record<string, any>
|
|
onInputsChange: (inputs: Record<string, any>) => void
|
|
plan?: string
|
|
}
|
|
|
|
const Welcome: FC<IWelcomeProps> = ({
|
|
conversationName,
|
|
hasSetInputs,
|
|
isPublicVersion,
|
|
siteInfo,
|
|
plan,
|
|
promptConfig,
|
|
onStartChat,
|
|
canEidtInpus,
|
|
savedInputs,
|
|
onInputsChange,
|
|
}) => {
|
|
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 tablet:!h-9 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 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'
|
|
/>
|
|
)
|
|
: (
|
|
<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}
|
|
/>
|
|
)}
|
|
</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 (!canEidtInpus) {
|
|
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 (!canEidtInpus || !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 && !canEidtInpus) || !hasVar)
|
|
return null
|
|
|
|
return (
|
|
<div
|
|
className='pt-[88px] mb-5'
|
|
>
|
|
{isPublicVersion ? renderHasSetInputsPublic() : renderHasSetInputsPrivate()}
|
|
</div>)
|
|
}
|
|
|
|
return (
|
|
<div className='relative mobile:min-h-[48px] 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'
|
|
href={siteInfo.privacy_policy}
|
|
target='_blank'>{t('share.chat.privacyPolicyMiddle')}</a>
|
|
{t('share.chat.privacyPolicyRight')}
|
|
</div>
|
|
: <div>
|
|
</div>}
|
|
{plan === 'basic' && <a className='flex items-center pr-3 space-x-3' href="https://dify.ai/" target="_blank">
|
|
<span className='uppercase'>{t('share.chat.powerBy')}</span>
|
|
<FootLogo />
|
|
</a>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div >
|
|
)
|
|
}
|
|
|
|
export default React.memo(Welcome)
|