forked from langgenius/dify
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Chore: refactor embedded chatbot (langgenius#5125)
- Loading branch information
Showing
15 changed files
with
1,051 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
46 changes: 46 additions & 0 deletions
46
web/app/components/base/chat/embedded-chatbot/config-panel/form-input.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
83 changes: 83 additions & 0 deletions
83
web/app/components/base/chat/embedded-chatbot/config-panel/form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.