123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- '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
- }
- const Welcome: FC<IWelcomeProps> = ({
- // conversationName,
- hasSetInputs,
- isPublicVersion,
- siteInfo,
- plan,
- promptConfig,
- onStartChat,
- canEditInputs,
- 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 = false
- 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 }) => {
- if (hasEmptyInput)
- return
- if (!inputs?.[key])
- hasEmptyInput = true
- })
- if (hasEmptyInput) {
- logError(t('appDebug.errorMessage.valueOfVarRequired'))
- 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 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)
|