| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 | '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 spanconst regex = /\{\{([^}]+)\}\}/gexport 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  canReplaceLogo?: boolean  customConfig?: {    remove_webapp_brand?: boolean    replace_webapp_logo?: string  }}const Welcome: FC<IWelcomeProps> = ({  conversationName,  hasSetInputs,  isPublicVersion,  siteInfo,  promptConfig,  onStartChat,  canEidtInpus,  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 }) }}              />            )}          </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' 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)
 |