Feat/embedding (#553)
Co-authored-by: Gillian97 <jinling.sunshine@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com>
This commit is contained in:
BIN
web/app/components/share/chatbot/welcome/icons/logo.png
Normal file
BIN
web/app/components/share/chatbot/welcome/icons/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
356
web/app/components/share/chatbot/welcome/index.tsx
Normal file
356
web/app/components/share/chatbot/welcome/index.tsx
Normal file
@@ -0,0 +1,356 @@
|
||||
'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)
|
||||
@@ -0,0 +1,74 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from 'classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
PencilIcon,
|
||||
} from '@heroicons/react/24/solid'
|
||||
import s from './style.module.css'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
export const AppInfo: FC<{ siteInfo: SiteInfo }> = ({ siteInfo }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div>
|
||||
<div className='flex items-center py-2 text-xl font-medium text-gray-700 rounded-md'>👏 {t('share.common.welcome')} {siteInfo.title}</div>
|
||||
<p className='text-sm text-gray-500'>{siteInfo.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const PromptTemplate: FC<{ html: string }> = ({ html }) => {
|
||||
return (
|
||||
<div
|
||||
className={' box-border text-sm text-gray-700'}
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
||||
export const StarIcon = () => (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.75 1C2.75 0.723858 2.52614 0.5 2.25 0.5C1.97386 0.5 1.75 0.723858 1.75 1V1.75H1C0.723858 1.75 0.5 1.97386 0.5 2.25C0.5 2.52614 0.723858 2.75 1 2.75H1.75V3.5C1.75 3.77614 1.97386 4 2.25 4C2.52614 4 2.75 3.77614 2.75 3.5V2.75H3.5C3.77614 2.75 4 2.52614 4 2.25C4 1.97386 3.77614 1.75 3.5 1.75H2.75V1Z" fill="#444CE7" />
|
||||
<path d="M2.75 8.5C2.75 8.22386 2.52614 8 2.25 8C1.97386 8 1.75 8.22386 1.75 8.5V9.25H1C0.723858 9.25 0.5 9.47386 0.5 9.75C0.5 10.0261 0.723858 10.25 1 10.25H1.75V11C1.75 11.2761 1.97386 11.5 2.25 11.5C2.52614 11.5 2.75 11.2761 2.75 11V10.25H3.5C3.77614 10.25 4 10.0261 4 9.75C4 9.47386 3.77614 9.25 3.5 9.25H2.75V8.5Z" fill="#444CE7" />
|
||||
<path d="M6.96667 1.32051C6.8924 1.12741 6.70689 1 6.5 1C6.29311 1 6.10759 1.12741 6.03333 1.32051L5.16624 3.57494C5.01604 3.96546 4.96884 4.078 4.90428 4.1688C4.8395 4.2599 4.7599 4.3395 4.6688 4.40428C4.578 4.46884 4.46546 4.51604 4.07494 4.66624L1.82051 5.53333C1.62741 5.60759 1.5 5.79311 1.5 6C1.5 6.20689 1.62741 6.39241 1.82051 6.46667L4.07494 7.33376C4.46546 7.48396 4.578 7.53116 4.6688 7.59572C4.7599 7.6605 4.8395 7.7401 4.90428 7.8312C4.96884 7.922 5.01604 8.03454 5.16624 8.42506L6.03333 10.6795C6.1076 10.8726 6.29311 11 6.5 11C6.70689 11 6.89241 10.8726 6.96667 10.6795L7.83376 8.42506C7.98396 8.03454 8.03116 7.922 8.09572 7.8312C8.1605 7.7401 8.2401 7.6605 8.3312 7.59572C8.422 7.53116 8.53454 7.48396 8.92506 7.33376L11.1795 6.46667C11.3726 6.39241 11.5 6.20689 11.5 6C11.5 5.79311 11.3726 5.60759 11.1795 5.53333L8.92506 4.66624C8.53454 4.51604 8.422 4.46884 8.3312 4.40428C8.2401 4.3395 8.1605 4.2599 8.09572 4.1688C8.03116 4.078 7.98396 3.96546 7.83376 3.57494L6.96667 1.32051Z" fill="#444CE7" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const ChatBtn: FC<{ onClick: () => void; className?: string }> = ({
|
||||
className,
|
||||
onClick,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Button
|
||||
type='primary'
|
||||
className={cn(className, `!p-0 space-x-2 flex items-center ${s.customBtn}`)}
|
||||
onClick={onClick}>
|
||||
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M18 10.5C18 14.366 14.418 17.5 10 17.5C8.58005 17.506 7.17955 17.1698 5.917 16.52L2 17.5L3.338 14.377C2.493 13.267 2 11.934 2 10.5C2 6.634 5.582 3.5 10 3.5C14.418 3.5 18 6.634 18 10.5ZM7 9.5H5V11.5H7V9.5ZM15 9.5H13V11.5H15V9.5ZM9 9.5H11V11.5H9V9.5Z" fill="white" />
|
||||
</svg>
|
||||
{t('share.chat.startChat')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export const EditBtn = ({ className, onClick }: { className?: string; onClick: () => void }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('px-2 flex space-x-1 items-center rounded-md cursor-pointer', className)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<PencilIcon className='w-3 h-3' />
|
||||
<span>{t('common.operation.edit')}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const FootLogo = () => (
|
||||
<div className={s.logo} />
|
||||
)
|
||||
29
web/app/components/share/chatbot/welcome/style.module.css
Normal file
29
web/app/components/share/chatbot/welcome/style.module.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.boxShodow {
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
}
|
||||
|
||||
.bgGrayColor {
|
||||
background-color: #F9FAFB;
|
||||
}
|
||||
|
||||
.headerBg {
|
||||
height: 3.5rem;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.formLabel {
|
||||
width: 120px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.customBtn {
|
||||
width: 136px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 48px;
|
||||
height: 20px;
|
||||
background: url(./icons/logo.png) center center no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
Reference in New Issue
Block a user