浏览代码

Fix:webapp UI issues (#15601)

tags/1.1.0
KVOJJJin 7 个月前
父节点
当前提交
efebbffe96
没有帐户链接到提交者的电子邮件

+ 40
- 6
web/app/components/base/chat/chat-with-history/chat-wrapper.tsx 查看文件

} from '@/service/share' } from '@/service/share'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import AnswerIcon from '@/app/components/base/answer-icon' import AnswerIcon from '@/app/components/base/answer-icon'
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
import { Markdown } from '@/app/components/base/markdown'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'


const ChatWrapper = () => { const ChatWrapper = () => {
currentChatInstanceRef, currentChatInstanceRef,
appData, appData,
themeBuilder, themeBuilder,
sidebarCollapseState,
clearChatList,
setClearChatList,
setIsResponding,
} = useChatWithHistoryContext() } = useChatWithHistoryContext()
const appConfig = useMemo(() => { const appConfig = useMemo(() => {
const config = appParams || {} const config = appParams || {}
setTargetMessageId, setTargetMessageId,
handleSend, handleSend,
handleStop, handleStop,
isResponding,
isResponding: respondingState,
suggestedQuestions, suggestedQuestions,
} = useChat( } = useChat(
appConfig, appConfig,
}, },
appPrevChatTree, appPrevChatTree,
taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId), taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId),
clearChatList,
setClearChatList,
) )
const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current
const inputDisabled = useMemo(() => { const inputDisabled = useMemo(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])


useEffect(() => {
setIsResponding(respondingState)
}, [respondingState, setIsResponding])

const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => { const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => {
const data: any = { const data: any = {
query: message, query: message,


const welcome = useMemo(() => { const welcome = useMemo(() => {
const welcomeMessage = chatList.find(item => item.isOpeningStatement) const welcomeMessage = chatList.find(item => item.isOpeningStatement)
if (respondingState)
return null
if (currentConversationId) if (currentConversationId)
return null return null
if (!welcomeMessage) if (!welcomeMessage)
return null return null
if (!collapsed && inputsForms.length > 0) if (!collapsed && inputsForms.length > 0)
return null return null
if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) {
return (
<div className='h-[50vh] py-12 px-4 flex items-center justify-center'>
<div className='grow max-w-[720px] flex gap-4'>
<AppIcon
size='xl'
iconType={appData?.site.icon_type}
icon={appData?.site.icon}
background={appData?.site.icon_background}
imageUrl={appData?.site.icon_url}
/>
<div className='grow px-4 py-3 bg-chat-bubble-bg text-text-primary rounded-2xl body-lg-regular'>
<Markdown content={welcomeMessage.content} />
<SuggestedQuestions item={welcomeMessage} />
</div>
</div>
</div>
)
}
return ( return (
<div className={cn('h-[50vh] py-12 flex flex-col items-center justify-center gap-3')}> <div className={cn('h-[50vh] py-12 flex flex-col items-center justify-center gap-3')}>
<AppIcon <AppIcon
background={appData?.site.icon_background} background={appData?.site.icon_background}
imageUrl={appData?.site.icon_url} imageUrl={appData?.site.icon_url}
/> />
<div className='text-text-tertiary body-2xl-regular'>{welcomeMessage.content}</div>
<Markdown className='!text-text-tertiary !body-2xl-regular' content={welcomeMessage.content} />
</div> </div>
) )
}, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length])
}, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState])


const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon) const answerIcon = (appData?.site && appData.site.use_icon_as_answer_icon)
? <AnswerIcon ? <AnswerIcon
appData={appData} appData={appData}
config={appConfig} config={appConfig}
chatList={messageList} chatList={messageList}
isResponding={isResponding}
chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[720px] ${isMobile && 'px-4'}`}
isResponding={respondingState}
chatContainerInnerClassName={`mx-auto pt-6 w-full max-w-[768px] ${isMobile && 'px-4'}`}
chatFooterClassName='pb-4' chatFooterClassName='pb-4'
chatFooterInnerClassName={`mx-auto w-full max-w-[720px] ${isMobile ? 'px-2' : 'px-4'}`}
chatFooterInnerClassName={`mx-auto w-full max-w-[768px] ${isMobile ? 'px-2' : 'px-4'}`}
onSend={doSend} onSend={doSend}
inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs} inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs}
inputsForm={inputsForms} inputsForm={inputsForms}
switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)} switchSibling={siblingMessageId => setTargetMessageId(siblingMessageId)}
inputDisabled={inputDisabled} inputDisabled={inputDisabled}
isMobile={isMobile} isMobile={isMobile}
sidebarCollapseState={sidebarCollapseState}
/> />
</div> </div>
) )

+ 8
- 0
web/app/components/base/chat/chat-with-history/context.tsx 查看文件

themeBuilder?: ThemeBuilder themeBuilder?: ThemeBuilder
sidebarCollapseState?: boolean sidebarCollapseState?: boolean
handleSidebarCollapse: (state: boolean) => void handleSidebarCollapse: (state: boolean) => void
clearChatList?: boolean
setClearChatList: (state: boolean) => void
isResponding?: boolean
setIsResponding: (state: boolean) => void,
} }


export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
currentChatInstanceRef: { current: { handleStop: () => {} } }, currentChatInstanceRef: { current: { handleStop: () => {} } },
sidebarCollapseState: false, sidebarCollapseState: false,
handleSidebarCollapse: () => {}, handleSidebarCollapse: () => {},
clearChatList: false,
setClearChatList: () => {},
isResponding: false,
setIsResponding: () => {},
}) })
export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext) export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)

+ 17
- 4
web/app/components/base/chat/chat-with-history/header/index.tsx 查看文件

useChatWithHistoryContext, useChatWithHistoryContext,
} from '../context' } from '../context'
import Operation from './operation' import Operation from './operation'
import ActionButton from '@/app/components/base/action-button'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown' import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown'
handleNewConversation, handleNewConversation,
sidebarCollapseState, sidebarCollapseState,
handleSidebarCollapse, handleSidebarCollapse,
isResponding,
} = useChatWithHistoryContext() } = useChatWithHistoryContext()
const { t } = useTranslation() const { t } = useTranslation()
const isSidebarCollapsed = sidebarCollapseState const isSidebarCollapsed = sidebarCollapseState
<div className='h-[14px] w-px bg-divider-regular'></div> <div className='h-[14px] w-px bg-divider-regular'></div>
</div> </div>
{isSidebarCollapsed && ( {isSidebarCollapsed && (
<ActionButton size='l' onClick={handleNewConversation}>
<RiEditBoxLine className='w-[18px] h-[18px]' />
</ActionButton>
<Tooltip
disabled={!!currentConversationId}
popupContent={t('share.chat.newChatTip')}
>
<div>
<ActionButton
size='l'
state={(!currentConversationId || isResponding) ? ActionButtonState.Disabled : ActionButtonState.Default}
disabled={!currentConversationId || isResponding}
onClick={handleNewConversation}
>
<RiEditBoxLine className='w-[18px] h-[18px]' />
</ActionButton>
</div>
</Tooltip>
)} )}
</div> </div>
<div className='flex items-center gap-1'> <div className='flex items-center gap-1'>

+ 14
- 12
web/app/components/base/chat/chat-with-history/hooks.tsx 查看文件

const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100)) const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100))
const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId)) const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId))


const [clearChatList, setClearChatList] = useState(false)
const [isResponding, setIsResponding] = useState(false)
const appPrevChatTree = useMemo( const appPrevChatTree = useMemo(
() => (currentConversationId && appChatListData?.data.length) () => (currentConversationId && appChatListData?.data.length)
? buildChatItemTree(getFormattedChatList(appChatListData.data)) ? buildChatItemTree(getFormattedChatList(appChatListData.data))
currentChatInstanceRef.current.handleStop() currentChatInstanceRef.current.handleStop()
setNewConversationId('') setNewConversationId('')
handleConversationIdInfoChange(conversationId) handleConversationIdInfoChange(conversationId)
}, [handleConversationIdInfoChange])
if (conversationId)
setClearChatList(false)
}, [handleConversationIdInfoChange, setClearChatList])
const handleNewConversation = useCallback(() => { const handleNewConversation = useCallback(() => {
currentChatInstanceRef.current.handleStop() currentChatInstanceRef.current.handleStop()
setNewConversationId('')

if (showNewConversationItemInList) {
handleChangeConversation('')
}
else if (currentConversationId) {
handleConversationIdInfoChange('')
setShowNewConversationItemInList(true)
handleNewConversationInputsChange({})
}
}, [handleChangeConversation, currentConversationId, handleConversationIdInfoChange, setShowNewConversationItemInList, showNewConversationItemInList, handleNewConversationInputsChange])
setShowNewConversationItemInList(true)
handleChangeConversation('')
handleNewConversationInputsChange({})
setClearChatList(true)
}, [handleChangeConversation, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList])
const handleUpdateConversationList = useCallback(() => { const handleUpdateConversationList = useCallback(() => {
mutateAppConversationData() mutateAppConversationData()
mutateAppPinnedConversationData() mutateAppPinnedConversationData()
currentChatInstanceRef, currentChatInstanceRef,
sidebarCollapseState, sidebarCollapseState,
handleSidebarCollapse, handleSidebarCollapse,
clearChatList,
setClearChatList,
isResponding,
setIsResponding,
} }
} }

+ 10
- 2
web/app/components/base/chat/chat-with-history/index.tsx 查看文件

{isMobile && ( {isMobile && (
<HeaderInMobile /> <HeaderInMobile />
)} )}
<div className={cn('relative grow p-2')}>
<div className={cn('relative grow p-2', isMobile && 'h-[calc(100%_-_56px)] p-0')}>
{isSidebarCollapsed && ( {isSidebarCollapsed && (
<div <div
className={cn( className={cn(
<Sidebar isPanel /> <Sidebar isPanel />
</div> </div>
)} )}
<div className='h-full flex flex-col bg-chatbot-bg rounded-2xl border-[0,5px] border-components-panel-border-subtle overflow-hidden'>
<div className={cn('h-full flex flex-col bg-chatbot-bg border-[0,5px] border-components-panel-border-subtle overflow-hidden', isMobile ? 'rounded-t-2xl' : 'rounded-2xl')}>
{!isMobile && <Header />} {!isMobile && <Header />}
{appChatListDataLoading && ( {appChatListDataLoading && (
<Loading type='app' /> <Loading type='app' />
currentChatInstanceRef, currentChatInstanceRef,
sidebarCollapseState, sidebarCollapseState,
handleSidebarCollapse, handleSidebarCollapse,
clearChatList,
setClearChatList,
isResponding,
setIsResponding,
} = useChatWithHistory(installedAppInfo) } = useChatWithHistory(installedAppInfo)


return ( return (
themeBuilder, themeBuilder,
sidebarCollapseState, sidebarCollapseState,
handleSidebarCollapse, handleSidebarCollapse,
clearChatList,
setClearChatList,
isResponding,
setIsResponding,
}}> }}>
<ChatWithHistory className={className} /> <ChatWithHistory className={className} />
</ChatWithHistoryContext.Provider> </ChatWithHistoryContext.Provider>

+ 2
- 1
web/app/components/base/chat/chat-with-history/sidebar/index.tsx 查看文件

sidebarCollapseState, sidebarCollapseState,
handleSidebarCollapse, handleSidebarCollapse,
isMobile, isMobile,
isResponding,
} = useChatWithHistoryContext() } = useChatWithHistoryContext()
const isSidebarCollapsed = sidebarCollapseState const isSidebarCollapsed = sidebarCollapseState


)} )}
</div> </div>
<div className='shrink-0 px-3 py-4'> <div className='shrink-0 px-3 py-4'>
<Button variant='secondary-accent' className='w-full justify-center' onClick={handleNewConversation}>
<Button variant='secondary-accent' disabled={isResponding} className='w-full justify-center' onClick={handleNewConversation}>
<RiEditBoxLine className='w-4 h-4 mr-1' /> <RiEditBoxLine className='w-4 h-4 mr-1' />
{t('share.chat.newChat')} {t('share.chat.newChat')}
</Button> </Button>

+ 1
- 1
web/app/components/base/chat/chat/answer/index.tsx 查看文件

</div> </div>
)} )}
</div> </div>
<div className='chat-answer-container group grow w-0 ml-4' ref={containerRef}>
<div className='chat-answer-container group grow w-0 ml-4 pb-4' ref={containerRef}>
<div className={cn('group relative pr-10', chatAnswerContainerInner)}> <div className={cn('group relative pr-10', chatAnswerContainerInner)}>
<div <div
ref={contentRef} ref={contentRef}

+ 2
- 2
web/app/components/base/chat/chat/answer/operation.tsx 查看文件

import { import {
RiClipboardLine, RiClipboardLine,
RiEditLine, RiEditLine,
RiReplay15Line,
RiResetLeftLine,
RiThumbDownLine, RiThumbDownLine,
RiThumbUpLine, RiThumbUpLine,
} from '@remixicon/react' } from '@remixicon/react'
</ActionButton> </ActionButton>
{!noChatInput && ( {!noChatInput && (
<ActionButton onClick={() => onRegenerate?.(item)}> <ActionButton onClick={() => onRegenerate?.(item)}>
<RiReplay15Line className='w-4 h-4' />
<RiResetLeftLine className='w-4 h-4' />
</ActionButton> </ActionButton>
)} )}
{(config?.supportAnnotation && config.annotation_reply?.enabled) && ( {(config?.supportAnnotation && config.annotation_reply?.enabled) && (

+ 10
- 2
web/app/components/base/chat/chat/hooks.ts 查看文件

}, },
prevChatTree?: ChatItemInTree[], prevChatTree?: ChatItemInTree[],
stopChat?: (taskId: string) => void, stopChat?: (taskId: string) => void,
clearChatList?: boolean,
clearChatListCallback?: (state: boolean) => void,
) => { ) => {
const { t } = useTranslation() const { t } = useTranslation()
const { formatTime } = useTimestamp() const { formatTime } = useTimestamp()
} }
else { else {
ret.unshift({ ret.unshift({
id: `${Date.now()}`,
id: 'opening-statement',
content: getIntroduction(config.opening_statement), content: getIntroduction(config.opening_statement),
isAnswer: true, isAnswer: true,
isOpeningStatement: true, isOpeningStatement: true,
suggestedQuestionsAbortControllerRef.current.abort() suggestedQuestionsAbortControllerRef.current.abort()
}, [stopChat, handleResponding]) }, [stopChat, handleResponding])


const handleRestart = useCallback(() => {
const handleRestart = useCallback((cb?: any) => {
conversationId.current = '' conversationId.current = ''
taskIdRef.current = '' taskIdRef.current = ''
handleStop() handleStop()
setChatTree([]) setChatTree([])
setSuggestQuestions([]) setSuggestQuestions([])
cb?.()
}, [handleStop]) }, [handleStop])


const updateCurrentQAOnTree = useCallback(({ const updateCurrentQAOnTree = useCallback(({
}) })
}, [chatList, updateChatTreeNode]) }, [chatList, updateChatTreeNode])


useEffect(() => {
if (clearChatList)
handleRestart(() => clearChatListCallback?.(false))
}, [clearChatList, clearChatListCallback, handleRestart])

return { return {
chatList, chatList,
setTargetMessageId, setTargetMessageId,

+ 8
- 1
web/app/components/base/chat/chat/index.tsx 查看文件

noSpacing?: boolean noSpacing?: boolean
inputDisabled?: boolean inputDisabled?: boolean
isMobile?: boolean isMobile?: boolean
sidebarCollapseState?: boolean
} }


const Chat: FC<ChatProps> = ({ const Chat: FC<ChatProps> = ({
noSpacing, noSpacing,
inputDisabled, inputDisabled,
isMobile, isMobile,
sidebarCollapseState,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({ const { currentLogItem, setCurrentLogItem, showPromptLogModal, setShowPromptLogModal, showAgentLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({
} }
}, []) }, [])


useEffect(() => {
if (!sidebarCollapseState)
setTimeout(() => handleWindowResize(), 200)
}, [sidebarCollapseState])

const hasTryToAsk = config?.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend const hasTryToAsk = config?.suggested_questions_after_answer?.enabled && !!suggestedQuestions?.length && onSend


return ( return (
</div> </div>
</div> </div>
<div <div
className={`absolute bottom-0 bg-chat-input-mask ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`}
className={`absolute bottom-0 bg-chat-input-mask flex justify-center ${(hasTryToAsk || !noChatInput || !noStopResponding) && chatFooterClassName}`}
ref={chatFooterRef} ref={chatFooterRef}
> >
<div <div

+ 37
- 6
web/app/components/base/chat/embedded-chatbot/chat-wrapper.tsx 查看文件

import AppIcon from '@/app/components/base/app-icon' import AppIcon from '@/app/components/base/app-icon'
import LogoAvatar from '@/app/components/base/logo/logo-embedded-chat-avatar' import LogoAvatar from '@/app/components/base/logo/logo-embedded-chat-avatar'
import AnswerIcon from '@/app/components/base/answer-icon' import AnswerIcon from '@/app/components/base/answer-icon'
import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested-questions'
import { Markdown } from '@/app/components/base/markdown'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'


const ChatWrapper = () => { const ChatWrapper = () => {
handleFeedback, handleFeedback,
currentChatInstanceRef, currentChatInstanceRef,
themeBuilder, themeBuilder,
clearChatList,
setClearChatList,
setIsResponding,
} = useEmbeddedChatbotContext() } = useEmbeddedChatbotContext()
const appConfig = useMemo(() => { const appConfig = useMemo(() => {
const config = appParams || {} const config = appParams || {}
setTargetMessageId, setTargetMessageId,
handleSend, handleSend,
handleStop, handleStop,
isResponding,
isResponding: respondingState,
suggestedQuestions, suggestedQuestions,
} = useChat( } = useChat(
appConfig, appConfig,
}, },
appPrevChatList, appPrevChatList,
taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId), taskId => stopChatMessageResponding('', taskId, isInstalledApp, appId),
clearChatList,
setClearChatList,
) )
const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current const inputsFormValue = currentConversationId ? currentConversationItem?.inputs : newConversationInputsRef?.current
const inputDisabled = useMemo(() => { const inputDisabled = useMemo(() => {
if (currentChatInstanceRef.current) if (currentChatInstanceRef.current)
currentChatInstanceRef.current.handleStop = handleStop currentChatInstanceRef.current.handleStop = handleStop
}, [currentChatInstanceRef, handleStop]) }, [currentChatInstanceRef, handleStop])
useEffect(() => {
setIsResponding(respondingState)
}, [respondingState, setIsResponding])


const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => { const doSend: OnSend = useCallback((message, files, isRegenerate = false, parentAnswer: ChatItem | null = null) => {
const data: any = { const data: any = {


const welcome = useMemo(() => { const welcome = useMemo(() => {
const welcomeMessage = chatList.find(item => item.isOpeningStatement) const welcomeMessage = chatList.find(item => item.isOpeningStatement)
if (respondingState)
return null
if (currentConversationId) if (currentConversationId)
return null return null
if (!welcomeMessage) if (!welcomeMessage)
return null return null
if (!collapsed && inputsForms.length > 0) if (!collapsed && inputsForms.length > 0)
return null return null
if (welcomeMessage.suggestedQuestions && welcomeMessage.suggestedQuestions?.length > 0) {
return (
<div className='h-[50vh] py-12 px-4 flex items-center justify-center'>
<div className='grow max-w-[720px] flex gap-4'>
<AppIcon
size='xl'
iconType={appData?.site.icon_type}
icon={appData?.site.icon}
background={appData?.site.icon_background}
imageUrl={appData?.site.icon_url}
/>
<div className='grow px-4 py-3 bg-chat-bubble-bg text-text-primary rounded-2xl body-lg-regular'>
<Markdown content={welcomeMessage.content} />
<SuggestedQuestions item={welcomeMessage} />
</div>
</div>
</div>
)
}
return ( return (
<div className={cn('h-[50vh] py-12 flex flex-col items-center justify-center gap-3')}> <div className={cn('h-[50vh] py-12 flex flex-col items-center justify-center gap-3')}>
<AppIcon <AppIcon
background={appData?.site.icon_background} background={appData?.site.icon_background}
imageUrl={appData?.site.icon_url} imageUrl={appData?.site.icon_url}
/> />
<div className='text-text-tertiary body-2xl-regular'>{welcomeMessage.content}</div>
<Markdown className='!text-text-tertiary !body-2xl-regular' content={welcomeMessage.content} />
</div> </div>
) )
}, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length])
}, [appData?.site.icon, appData?.site.icon_background, appData?.site.icon_type, appData?.site.icon_url, chatList, collapsed, currentConversationId, inputsForms.length, respondingState])


const answerIcon = isDify() const answerIcon = isDify()
? <LogoAvatar className='relative shrink-0' /> ? <LogoAvatar className='relative shrink-0' />
appData={appData} appData={appData}
config={appConfig} config={appConfig}
chatList={messageList} chatList={messageList}
isResponding={isResponding}
chatContainerInnerClassName={cn('mx-auto w-full max-w-full tablet:px-4', isMobile && 'px-4')}
isResponding={respondingState}
chatContainerInnerClassName={cn('mx-auto w-full max-w-full pt-4 tablet:px-4', isMobile && 'px-4')}
chatFooterClassName={cn('pb-4', !isMobile && 'rounded-b-2xl')} chatFooterClassName={cn('pb-4', !isMobile && 'rounded-b-2xl')}
chatFooterInnerClassName={cn('mx-auto w-full max-w-full tablet:px-4', isMobile && 'px-2')}
chatFooterInnerClassName={cn('mx-auto w-full max-w-full px-4', isMobile && 'px-2')}
onSend={doSend} onSend={doSend}
inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs} inputs={currentConversationId ? currentConversationItem?.inputs as any : newConversationInputs}
inputsForm={inputsForms} inputsForm={inputsForms}

+ 8
- 0
web/app/components/base/chat/embedded-chatbot/context.tsx 查看文件

handleFeedback: (messageId: string, feedback: Feedback) => void handleFeedback: (messageId: string, feedback: Feedback) => void
currentChatInstanceRef: RefObject<{ handleStop: () => void }> currentChatInstanceRef: RefObject<{ handleStop: () => void }>
themeBuilder?: ThemeBuilder themeBuilder?: ThemeBuilder
clearChatList?: boolean
setClearChatList: (state: boolean) => void
isResponding?: boolean
setIsResponding: (state: boolean) => void,
} }


export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({
isInstalledApp: false, isInstalledApp: false,
handleFeedback: () => {}, handleFeedback: () => {},
currentChatInstanceRef: { current: { handleStop: () => {} } }, currentChatInstanceRef: { current: { handleStop: () => {} } },
clearChatList: false,
setClearChatList: () => {},
isResponding: false,
setIsResponding: () => {},
}) })
export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext) export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext)

+ 14
- 12
web/app/components/base/chat/embedded-chatbot/hooks.tsx 查看文件

const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100)) const { data: appConversationData, isLoading: appConversationDataLoading, mutate: mutateAppConversationData } = useSWR(['appConversationData', isInstalledApp, appId, false], () => fetchConversations(isInstalledApp, appId, undefined, false, 100))
const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId)) const { data: appChatListData, isLoading: appChatListDataLoading } = useSWR(chatShouldReloadKey ? ['appChatList', chatShouldReloadKey, isInstalledApp, appId] : null, () => fetchChatList(chatShouldReloadKey, isInstalledApp, appId))


const [clearChatList, setClearChatList] = useState(false)
const [isResponding, setIsResponding] = useState(false)
const appPrevChatList = useMemo( const appPrevChatList = useMemo(
() => (currentConversationId && appChatListData?.data.length) () => (currentConversationId && appChatListData?.data.length)
? buildChatItemTree(getFormattedChatList(appChatListData.data)) ? buildChatItemTree(getFormattedChatList(appChatListData.data))
currentChatInstanceRef.current.handleStop() currentChatInstanceRef.current.handleStop()
setNewConversationId('') setNewConversationId('')
handleConversationIdInfoChange(conversationId) handleConversationIdInfoChange(conversationId)
}, [handleConversationIdInfoChange])
if (conversationId)
setClearChatList(false)
}, [handleConversationIdInfoChange, setClearChatList])
const handleNewConversation = useCallback(() => { const handleNewConversation = useCallback(() => {
currentChatInstanceRef.current.handleStop() currentChatInstanceRef.current.handleStop()
setNewConversationId('')

if (showNewConversationItemInList) {
handleChangeConversation('')
}
else if (currentConversationId) {
handleConversationIdInfoChange('')
setShowNewConversationItemInList(true)
handleNewConversationInputsChange({})
}
}, [handleChangeConversation, currentConversationId, handleConversationIdInfoChange, setShowNewConversationItemInList, showNewConversationItemInList, handleNewConversationInputsChange])
setShowNewConversationItemInList(true)
handleChangeConversation('')
handleNewConversationInputsChange({})
setClearChatList(true)
}, [handleChangeConversation, setShowNewConversationItemInList, handleNewConversationInputsChange, setClearChatList])


const handleNewConversationCompleted = useCallback((newConversationId: string) => { const handleNewConversationCompleted = useCallback((newConversationId: string) => {
setNewConversationId(newConversationId) setNewConversationId(newConversationId)
chatShouldReloadKey, chatShouldReloadKey,
handleFeedback, handleFeedback,
currentChatInstanceRef, currentChatInstanceRef,
clearChatList,
setClearChatList,
isResponding,
setIsResponding,
} }
} }

+ 8
- 0
web/app/components/base/chat/embedded-chatbot/index.tsx 查看文件

appId, appId,
handleFeedback, handleFeedback,
currentChatInstanceRef, currentChatInstanceRef,
clearChatList,
setClearChatList,
isResponding,
setIsResponding,
} = useEmbeddedChatbot() } = useEmbeddedChatbot()


return <EmbeddedChatbotContext.Provider value={{ return <EmbeddedChatbotContext.Provider value={{
handleFeedback, handleFeedback,
currentChatInstanceRef, currentChatInstanceRef,
themeBuilder, themeBuilder,
clearChatList,
setClearChatList,
isResponding,
setIsResponding,
}}> }}>
<Chatbot /> <Chatbot />
</EmbeddedChatbotContext.Provider> </EmbeddedChatbotContext.Provider>

+ 1
- 1
web/app/styles/markdown.scss 查看文件

display: block; display: block;
width: max-content; width: max-content;
max-width: 100%; max-width: 100%;
overflow: hidden;
overflow: auto;
border: 1px solid var(--color-divider-regular); border: 1px solid var(--color-divider-regular);
border-radius: 8px; border-radius: 8px;
} }

+ 1
- 0
web/i18n/en-US/share-app.ts 查看文件

}, },
chat: { chat: {
newChat: 'Start New chat', newChat: 'Start New chat',
newChatTip: 'Already in a new chat',
chatSettingsTitle: 'New chat setup', chatSettingsTitle: 'New chat setup',
chatFormTip: 'Chat settings cannot be modified after the chat has started.', chatFormTip: 'Chat settings cannot be modified after the chat has started.',
pinnedTitle: 'Pinned', pinnedTitle: 'Pinned',

+ 1
- 0
web/i18n/zh-Hans/share-app.ts 查看文件

}, },
chat: { chat: {
newChat: '开启新对话', newChat: '开启新对话',
newChatTip: '已在新对话中',
chatSettingsTitle: '新对话设置', chatSettingsTitle: '新对话设置',
chatFormTip: '对话开始后,对话设置将无法修改。', chatFormTip: '对话开始后,对话设置将无法修改。',
pinnedTitle: '已置顶', pinnedTitle: '已置顶',

正在加载...
取消
保存