Skip to content

Commit

Permalink
Chore: refactor embedded chatbot (langgenius#5125)
Browse files Browse the repository at this point in the history
  • Loading branch information
JzoNgKVO authored Jun 14, 2024
1 parent 54e02b8 commit 4289f17
Show file tree
Hide file tree
Showing 15 changed files with 1,051 additions and 20 deletions.
4 changes: 2 additions & 2 deletions web/app/(shareLayout)/chatbot/[token]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { FC } from 'react'
import React, { useEffect } from 'react'
import cn from 'classnames'
import type { IMainProps } from '@/app/components/share/chat'
import Main from '@/app/components/share/chatbot'
import EmbeddedChatbot from '@/app/components/base/chat/embedded-chatbot'
import Loading from '@/app/components/base/loading'
import { fetchSystemFeatures } from '@/service/share'
import LogoSite from '@/app/components/base/logo/logo-site'
Expand Down Expand Up @@ -77,7 +77,7 @@ const Chatbot: FC<IMainProps> = () => {
</div>
</div>
)
: <Main />
: <EmbeddedChatbot />
}
</>
)}
Expand Down
4 changes: 2 additions & 2 deletions web/app/components/app/chat/icon-component/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const TryToAskIcon = (
)

export const ReplayIcon = ({ className }: SVGProps<SVGElement>) => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.33301 6.66667C1.33301 6.66667 2.66966 4.84548 3.75556 3.75883C4.84147 2.67218 6.34207 2 7.99967 2C11.3134 2 13.9997 4.68629 13.9997 8C13.9997 11.3137 11.3134 14 7.99967 14C5.26428 14 2.95642 12.1695 2.23419 9.66667M1.33301 6.66667V2.66667M1.33301 6.66667H5.33301" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M1.33301 6.66667C1.33301 6.66667 2.66966 4.84548 3.75556 3.75883C4.84147 2.67218 6.34207 2 7.99967 2C11.3134 2 13.9997 4.68629 13.9997 8C13.9997 11.3137 11.3134 14 7.99967 14C5.26428 14 2.95642 12.1695 2.23419 9.66667M1.33301 6.66667V2.66667M1.33301 6.66667H5.33301" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
6 changes: 3 additions & 3 deletions web/app/components/base/app-unavailable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { useTranslation } from 'react-i18next'

type IAppUnavailableProps = {
code?: number
isUnknwonReason?: boolean
isUnknownReason?: boolean
unknownReason?: string
}

const AppUnavailable: FC<IAppUnavailableProps> = ({
code = 404,
isUnknwonReason,
isUnknownReason,
unknownReason,
}) => {
const { t } = useTranslation()
Expand All @@ -22,7 +22,7 @@ const AppUnavailable: FC<IAppUnavailableProps> = ({
style={{
borderRight: '1px solid rgba(0,0,0,.3)',
}}>{code}</h1>
<div className='text-sm'>{unknownReason || (isUnknwonReason ? t('share.common.appUnkonwError') : t('share.common.appUnavailable'))}</div>
<div className='text-sm'>{unknownReason || (isUnknownReason ? t('share.common.appUnkonwError') : t('share.common.appUnavailable'))}</div>
</div>
)
}
Expand Down
18 changes: 9 additions & 9 deletions web/app/components/base/chat/chat-with-history/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,12 @@ const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
installedAppInfo,
className,
}) => {
const [inited, setInited] = useState(false)
const [initialized, setInitialized] = useState(false)
const [appUnavailable, setAppUnavailable] = useState<boolean>(false)
const [isUnknwonReason, setIsUnknwonReason] = useState<boolean>(false)
const [isUnknownReason, setIsUnknownReason] = useState<boolean>(false)

useAsyncEffect(async () => {
if (!inited) {
if (!initialized) {
if (!installedAppInfo) {
try {
await checkOrSetAccessToken()
Expand All @@ -196,21 +196,21 @@ const ChatWithHistoryWrapWithCheckToken: FC<ChatWithHistoryWrapProps> = ({
setAppUnavailable(true)
}
else {
setIsUnknwonReason(true)
setIsUnknownReason(true)
setAppUnavailable(true)
}
}
}
setInited(true)
setInitialized(true)
}
}, [])

if (appUnavailable)
return <AppUnavailable isUnknwonReason={isUnknwonReason} />

if (!inited)
if (!initialized)
return null

if (appUnavailable)
return <AppUnavailable isUnknownReason={isUnknownReason} />

return (
<ChatWithHistoryWrap
installedAppInfo={installedAppInfo}
Expand Down
135 changes: 135 additions & 0 deletions web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { useCallback, useEffect, useMemo } from 'react'
import cn from 'classnames'
import Chat from '../chat'
import type {
ChatConfig,
OnSend,
} from '../types'
import { useChat } from '../chat/hooks'
import { useEmbeddedChatbotContext } from './context'
import ConfigPanel from './config-panel'
import { isDify } from './utils'
import {
fetchSuggestedQuestions,
getUrl,
stopChatMessageResponding,
} from '@/service/share'
import LogoAvatar from '@/app/components/base/logo/logo-embeded-chat-avatar'

const ChatWrapper = () => {
const {
appParams,
appPrevChatList,
currentConversationId,
currentConversationItem,
inputsForms,
newConversationInputs,
handleNewConversationCompleted,
isMobile,
isInstalledApp,
appId,
appMeta,
handleFeedback,
currentChatInstanceRef,
} = useEmbeddedChatbotContext()
const appConfig = useMemo(() => {
const config = appParams || {}

return {
...config,
supportFeedback: true,
opening_statement: currentConversationId ? currentConversationItem?.introduction : (config as any).opening_statement,
} as ChatConfig
}, [appParams, currentConversationItem?.introduction, currentConversationId])
const {
chatList,
handleSend,
handleStop,
isResponding,
suggestedQuestions,
} = useChat(
appConfig,
{
inputs: (currentConversationId ? currentConversationItem?.inputs : newConversationInputs) as any,
promptVariables: inputsForms,
},
appPrevChatList,
taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId),
)

useEffect(() => {
if (currentChatInstanceRef.current)
currentChatInstanceRef.current.handleStop = handleStop
}, [])

const doSend: OnSend = useCallback((message, files) => {
const data: any = {
query: message,
inputs: currentConversationId ? currentConversationItem?.inputs : newConversationInputs,
conversation_id: currentConversationId,
}

if (appConfig?.file_upload?.image.enabled && files?.length)
data.files = files

handleSend(
getUrl('chat-messages', isInstalledApp, appId || ''),
data,
{
onGetSuggestedQuestions: responseItemId => fetchSuggestedQuestions(responseItemId, isInstalledApp, appId),
onConversationComplete: currentConversationId ? undefined : handleNewConversationCompleted,
isPublicAPI: !isInstalledApp,
},
)
}, [
appConfig,
currentConversationId,
currentConversationItem,
handleSend,
newConversationInputs,
handleNewConversationCompleted,
isInstalledApp,
appId,
])
const chatNode = useMemo(() => {
if (inputsForms.length) {
return (
<>
{!currentConversationId && (
<div className={cn('mx-auto w-full max-w-[720px] tablet:px-4', isMobile && 'px-4')}>
<div className='mb-6' />
<ConfigPanel />
<div
className='my-6 h-[1px]'
style={{ background: 'linear-gradient(90deg, rgba(242, 244, 247, 0.00) 0%, #F2F4F7 49.17%, rgba(242, 244, 247, 0.00) 100%)' }}
/>
</div>
)}
</>
)
}

return <div className='mb-6' />
}, [currentConversationId, inputsForms, isMobile])

return (
<Chat
config={appConfig}
chatList={chatList}
isResponding={isResponding}
chatContainerInnerClassName={cn('mx-auto w-full max-w-[720px] tablet:px-4', isMobile && 'px-4')}
chatFooterClassName='pb-4'
chatFooterInnerClassName={cn('mx-auto w-full max-w-[720px] tablet:px-4', isMobile && 'px-4')}
onSend={doSend}
onStopResponding={handleStop}
chatNode={chatNode}
allToolIcons={appMeta?.tool_icons || {}}
onFeedback={handleFeedback}
suggestedQuestions={suggestedQuestions}
answerIcon={isDify() ? <LogoAvatar className='relative shrink-0' /> : null}
hideProcessDetail
/>
)
}

export default ChatWrapper
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { memo } from 'react'

type InputProps = {
form: any
value: string
onChange: (variable: string, value: string) => void
}
const FormInput: FC<InputProps> = ({
form,
value,
onChange,
}) => {
const { t } = useTranslation()
const {
type,
label,
required,
max_length,
variable,
} = form

if (type === 'paragraph') {
return (
<textarea
value={value}
className='grow h-[104px] rounded-lg bg-gray-100 px-2.5 py-2 outline-none appearance-none resize-none'
onChange={e => onChange(variable, e.target.value)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}

return (
<input
className='grow h-9 rounded-lg bg-gray-100 px-2.5 outline-none appearance-none'
value={value || ''}
maxLength={max_length}
onChange={e => onChange(variable, e.target.value)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}

export default memo(FormInput)
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useEmbeddedChatbotContext } from '../context'
import Input from './form-input'
import { PortalSelect } from '@/app/components/base/select'

const Form = () => {
const { t } = useTranslation()
const {
inputsForms,
newConversationInputs,
handleNewConversationInputsChange,
isMobile,
} = useEmbeddedChatbotContext()

const handleFormChange = useCallback((variable: string, value: string) => {
handleNewConversationInputsChange({
...newConversationInputs,
[variable]: value,
})
}, [newConversationInputs, handleNewConversationInputsChange])

const renderField = (form: any) => {
const {
label,
required,
variable,
options,
} = form

if (form.type === 'text-input' || form.type === 'paragraph') {
return (
<Input
form={form}
value={newConversationInputs[variable]}
onChange={handleFormChange}
/>
)
}
if (form.type === 'number') {
return (
<input
className="grow h-9 rounded-lg bg-gray-100 px-2.5 outline-none appearance-none"
type="number"
value={newConversationInputs[variable] || ''}
onChange={e => handleFormChange(variable, e.target.value)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}

return (
<PortalSelect
popupClassName='w-[200px]'
value={newConversationInputs[variable]}
items={options.map((option: string) => ({ value: option, name: option }))}
onSelect={item => handleFormChange(variable, item.value as string)}
placeholder={`${label}${!required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
/>
)
}

if (!inputsForms.length)
return null

return (
<div className='mb-4 py-2'>
{
inputsForms.map(form => (
<div
key={form.variable}
className={`flex mb-3 last-of-type:mb-0 text-sm text-gray-900 ${isMobile && '!flex-wrap'}`}
>
<div className={`shrink-0 mr-2 py-2 w-[128px] ${isMobile && '!w-full'}`}>{form.label}</div>
{renderField(form)}
</div>
))
}
</div>
)
}

export default Form
Loading

0 comments on commit 4289f17

Please sign in to comment.