This commit is contained in:
dataCenter430 2026-03-24 19:42:19 +08:00 committed by GitHub
commit 3b30fc4d4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 180 additions and 130 deletions

View File

@ -20,6 +20,8 @@ import { createContext, useContext } from 'use-context-selector'
export type ChatWithHistoryContextValue = {
appMeta?: AppMeta | null
appData?: AppData | null
siteDescription?: string
showSiteDescription: boolean
appParams?: ChatConfig
appChatListDataLoading?: boolean
currentConversationId: string
@ -95,5 +97,7 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>
setCurrentConversationInputs: noop,
allInputsHidden: false,
initUserVariables: {},
siteDescription: '',
showSiteDescription: false,
})
export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)

View File

@ -6,6 +6,7 @@ import AppIcon from '@/app/components/base/app-icon'
import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
import Confirm from '@/app/components/base/confirm'
import { cn } from '@/utils/classnames'
import { useChatWithHistoryContext } from './context'
import MobileOperationDropdown from './header/mobile-operation-dropdown'
import Operation from './header/operation'
@ -14,6 +15,8 @@ import Sidebar from './sidebar'
const HeaderInMobile = () => {
const {
appData,
siteDescription,
showSiteDescription,
currentConversationId,
currentConversationItem,
pinnedConversationList,
@ -63,43 +66,50 @@ const HeaderInMobile = () => {
return (
<>
<div className="flex shrink-0 items-center gap-1 bg-mask-top2bottom-gray-50-to-transparent px-2 py-3">
<ActionButton size="l" className="shrink-0" onClick={() => setShowSidebar(true)}>
<div className="i-ri-menu-line h-[18px] w-[18px]" />
</ActionButton>
<div className="flex grow items-center justify-center">
{!currentConversationId && (
<>
<AppIcon
className="mr-2"
size="tiny"
icon={appData?.site.icon}
iconType={appData?.site.icon_type}
imageUrl={appData?.site.icon_url}
background={appData?.site.icon_background}
<div className={cn('shrink-0 bg-mask-top2bottom-gray-50-to-transparent px-2', showSiteDescription ? 'py-2' : 'py-3')}>
<div className="flex items-center gap-1">
<ActionButton size="l" className="shrink-0" onClick={() => setShowSidebar(true)}>
<div className="i-ri-menu-line h-[18px] w-[18px]" />
</ActionButton>
<div className="flex grow items-center justify-center">
{!currentConversationId && (
<>
<AppIcon
className="mr-2"
size="tiny"
icon={appData?.site.icon}
iconType={appData?.site.icon_type}
imageUrl={appData?.site.icon_url}
background={appData?.site.icon_background}
/>
<div className="truncate text-text-secondary system-md-semibold">
{appData?.site.title}
</div>
</>
)}
{currentConversationId && (
<Operation
title={currentConversationItem?.name || ''}
isPinned={!!isPin}
togglePin={() => handleOperate(isPin ? 'unpin' : 'pin')}
isShowDelete
isShowRenameConversation
onRenameConversation={() => handleOperate('rename')}
onDelete={() => handleOperate('delete')}
/>
<div className="truncate text-text-secondary system-md-semibold">
{appData?.site.title}
</div>
</>
)}
{currentConversationId && (
<Operation
title={currentConversationItem?.name || ''}
isPinned={!!isPin}
togglePin={() => handleOperate(isPin ? 'unpin' : 'pin')}
isShowDelete
isShowRenameConversation
onRenameConversation={() => handleOperate('rename')}
onDelete={() => handleOperate('delete')}
/>
)}
)}
</div>
<MobileOperationDropdown
handleResetChat={handleNewConversation}
handleViewChatSettings={() => setShowChatSettings(true)}
hideViewChatSettings={inputsForms.length < 1}
/>
</div>
<MobileOperationDropdown
handleResetChat={handleNewConversation}
handleViewChatSettings={() => setShowChatSettings(true)}
hideViewChatSettings={inputsForms.length < 1}
/>
{showSiteDescription && (
<div className="system-xs-regular mt-1 line-clamp-2 break-words px-1 text-center text-text-tertiary">
{siteDescription}
</div>
)}
</div>
{showSidebar && (
<div

View File

@ -21,6 +21,8 @@ import Operation from './operation'
const Header = () => {
const {
appData,
siteDescription,
showSiteDescription,
currentConversationId,
currentConversationItem,
inputsForms,
@ -74,72 +76,79 @@ const Header = () => {
return (
<>
<div className="flex h-14 shrink-0 items-center justify-between p-3">
<div className={cn('flex items-center gap-1 transition-all duration-200 ease-in-out', !isSidebarCollapsed && 'user-select-none opacity-0')}>
<ActionButton className={cn(!isSidebarCollapsed && 'cursor-default')} size="l" onClick={() => handleSidebarCollapse(false)}>
<RiLayoutRight2Line className="h-[18px] w-[18px]" />
</ActionButton>
<div className="mr-1 shrink-0">
<AppIcon
size="large"
iconType={appData?.site.icon_type}
icon={appData?.site.icon}
background={appData?.site.icon_background}
imageUrl={appData?.site.icon_url}
/>
</div>
{!currentConversationId && (
<div className={cn('grow truncate text-text-secondary system-md-semibold')}>{appData?.site.title}</div>
)}
{currentConversationId && currentConversationItem && isSidebarCollapsed && (
<>
<div className="p-1 text-divider-deep">/</div>
<Operation
title={currentConversationItem?.name || ''}
isPinned={!!isPin}
togglePin={() => handleOperate(isPin ? 'unpin' : 'pin')}
isShowDelete
isShowRenameConversation
onRenameConversation={() => handleOperate('rename')}
onDelete={() => handleOperate('delete')}
<div className={cn('shrink-0 p-3', showSiteDescription && 'flex flex-col gap-1')}>
<div className="flex h-14 items-center justify-between">
<div className={cn('flex items-center gap-1 transition-all duration-200 ease-in-out', !isSidebarCollapsed && 'user-select-none opacity-0')}>
<ActionButton className={cn(!isSidebarCollapsed && 'cursor-default')} size="l" onClick={() => handleSidebarCollapse(false)}>
<RiLayoutRight2Line className="h-[18px] w-[18px]" />
</ActionButton>
<div className="mr-1 shrink-0">
<AppIcon
size="large"
iconType={appData?.site.icon_type}
icon={appData?.site.icon}
background={appData?.site.icon_background}
imageUrl={appData?.site.icon_url}
/>
</>
)}
<div className="flex items-center px-1">
<div className="h-[14px] w-px bg-divider-regular"></div>
</div>
{!currentConversationId && (
<div className={cn('system-md-semibold grow truncate text-text-secondary')}>{appData?.site.title}</div>
)}
{currentConversationId && currentConversationItem && isSidebarCollapsed && (
<>
<div className="p-1 text-divider-deep">/</div>
<Operation
title={currentConversationItem?.name || ''}
isPinned={!!isPin}
togglePin={() => handleOperate(isPin ? 'unpin' : 'pin')}
isShowDelete
isShowRenameConversation
onRenameConversation={() => handleOperate('rename')}
onDelete={() => handleOperate('delete')}
/>
</>
)}
<div className="flex items-center px-1">
<div className="h-[14px] w-px bg-divider-regular"></div>
</div>
{isSidebarCollapsed && (
<Tooltip
disabled={!!currentConversationId}
popupContent={t('chat.newChatTip', { ns: 'share' })}
>
<div>
<ActionButton
size="l"
state={(!currentConversationId || isResponding) ? ActionButtonState.Disabled : ActionButtonState.Default}
disabled={!currentConversationId || isResponding}
onClick={handleNewConversation}
>
<RiEditBoxLine className="h-[18px] w-[18px]" />
</ActionButton>
</div>
</Tooltip>
)}
</div>
{isSidebarCollapsed && (
<Tooltip
disabled={!!currentConversationId}
popupContent={t('chat.newChatTip', { ns: 'share' })}
>
<div>
<ActionButton
size="l"
state={(!currentConversationId || isResponding) ? ActionButtonState.Disabled : ActionButtonState.Default}
disabled={!currentConversationId || isResponding}
onClick={handleNewConversation}
>
<RiEditBoxLine className="h-[18px] w-[18px]" />
<div className="flex items-center gap-1">
{currentConversationId && (
<Tooltip
popupContent={t('chat.resetChat', { ns: 'share' })}
>
<ActionButton size="l" onClick={handleNewConversation}>
<RiResetLeftLine className="h-[18px] w-[18px]" />
</ActionButton>
</div>
</Tooltip>
)}
</div>
<div className="flex items-center gap-1">
{currentConversationId && (
<Tooltip
popupContent={t('chat.resetChat', { ns: 'share' })}
>
<ActionButton size="l" onClick={handleNewConversation}>
<RiResetLeftLine className="h-[18px] w-[18px]" />
</ActionButton>
</Tooltip>
)}
{currentConversationId && inputsForms.length > 0 && (
<ViewFormDropdown />
)}
</Tooltip>
)}
{currentConversationId && inputsForms.length > 0 && (
<ViewFormDropdown />
)}
</div>
</div>
{showSiteDescription && (
<div className="system-xs-regular max-h-[2.5rem] overflow-y-auto break-words text-text-tertiary">
{siteDescription}
</div>
)}
</div>
{!!showConfirm && (
<Confirm

View File

@ -193,6 +193,9 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
}
}, [appId, conversationIdInfo, setConversationIdInfo, userId])
const siteDescription = appData?.site?.description || ''
const showSiteDescription = !currentConversationId && !!siteDescription
const [newConversationId, setNewConversationId] = useState('')
const chatShouldReloadKey = useMemo(() => {
if (currentConversationId === newConversationId)
@ -587,6 +590,8 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
currentConversationItem,
handleConversationIdInfoChange,
appData,
siteDescription,
showSiteDescription,
appParams: appParams || {} as ChatConfig,
appMeta,
appPinnedConversationData,

View File

@ -147,11 +147,15 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
setCurrentConversationInputs,
allInputsHidden,
initUserVariables,
siteDescription,
showSiteDescription,
} = useChatWithHistory(installedAppInfo)
return (
<ChatWithHistoryContext.Provider value={{
appData,
siteDescription,
showSiteDescription,
appParams,
appMeta,
appChatListDataLoading,

View File

@ -21,6 +21,7 @@ export type IHeaderProps = {
allowResetChat?: boolean
customerIcon?: React.ReactNode
title: string
description?: string
theme?: Theme
onCreateNewChat?: () => void
}
@ -29,6 +30,7 @@ const Header: FC<IHeaderProps> = ({
allowResetChat,
customerIcon,
title,
description,
theme,
onCreateNewChat,
}) => {
@ -142,47 +144,57 @@ const Header: FC<IHeaderProps> = ({
return (
<div
className={cn('flex h-14 shrink-0 items-center justify-between rounded-t-2xl px-3')}
className={cn('shrink-0 rounded-t-2xl px-3', description ? 'pb-2' : '')}
style={CssTransform(theme?.headerBorderBottomStyle ?? '')}
>
<div className="flex grow items-center space-x-3">
{customerIcon}
<div
className="truncate system-md-semibold"
style={CssTransform(theme?.colorFontOnHeaderStyle ?? '')}
>
{title}
<div className="flex h-14 items-center justify-between">
<div className="flex grow items-center space-x-3">
{customerIcon}
<div
className="system-md-semibold truncate"
style={CssTransform(theme?.colorFontOnHeaderStyle ?? '')}
>
{title}
</div>
</div>
</div>
<div className="flex items-center gap-1">
{
showToggleExpandButton && (
<div className="flex items-center gap-1">
{
showToggleExpandButton && (
<Tooltip
popupContent={expanded ? t('chat.collapse', { ns: 'share' }) : t('chat.expand', { ns: 'share' })}
>
<ActionButton size="l" onClick={handleToggleExpand}>
{
expanded
? <RiCollapseDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
: <RiExpandDiagonal2Line className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
}
</ActionButton>
</Tooltip>
)
}
{currentConversationId && allowResetChat && (
<Tooltip
popupContent={expanded ? t('chat.collapse', { ns: 'share' }) : t('chat.expand', { ns: 'share' })}
popupContent={t('chat.resetChat', { ns: 'share' })}
>
<ActionButton size="l" onClick={handleToggleExpand} data-testid="mobile-expand-button">
{
expanded
? <div className={cn('i-ri-collapse-diagonal-2-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
: <div className={cn('i-ri-expand-diagonal-2-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
}
<ActionButton size="l" onClick={onCreateNewChat}>
<RiResetLeftLine className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
</ActionButton>
</Tooltip>
)
}
{currentConversationId && allowResetChat && (
<Tooltip
popupContent={t('chat.resetChat', { ns: 'share' })}
>
<ActionButton size="l" onClick={onCreateNewChat} data-testid="mobile-reset-chat-button">
<div className={cn('i-ri-reset-left-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
</ActionButton>
</Tooltip>
)}
{currentConversationId && inputsForms.length > 0 && !allInputsHidden && (
<ViewFormDropdown iconColor={theme?.colorPathOnHeader} />
)}
)}
{currentConversationId && inputsForms.length > 0 && !allInputsHidden && (
<ViewFormDropdown iconColor={theme?.colorPathOnHeader} />
)}
</div>
</div>
{description && (
<div
className="system-xs-regular line-clamp-2 break-words text-text-tertiary"
style={CssTransform(theme?.colorFontOnHeaderStyle ?? '')}
>
{description}
</div>
)}
</div>
)
}

View File

@ -60,10 +60,16 @@ const Chatbot = () => {
isMobile={isMobile}
allowResetChat={allowResetChat}
title={site?.title || ''}
description={site?.description}
customerIcon={isDify() ? difyIcon : ''}
theme={themeBuilder?.theme}
onCreateNewChat={handleNewConversation}
/>
{!isMobile && site?.description && (
<div className="system-xs-regular shrink-0 break-words px-3 pb-2 text-text-tertiary">
{site.description}
</div>
)}
<div className={cn('flex grow flex-col overflow-y-auto', isMobile && 'm-[0.5px] !h-[calc(100vh_-_3rem)] rounded-2xl bg-chatbot-bg')}>
{appChatListDataLoading && (
<Loading type="app" />