dify/web/app/components/base/chat/chat-with-history/header/index.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

205 lines
7.7 KiB
TypeScript

import type { ConversationItem } from '@/models/share'
import {
AlertDialog,
AlertDialogActions,
AlertDialogCancelButton,
AlertDialogConfirmButton,
AlertDialogContent,
AlertDialogDescription,
AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiEditBoxLine, RiLayoutRight2Line, RiResetLeftLine } from '@remixicon/react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import AppIcon from '@/app/components/base/app-icon'
import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown'
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
import { useChatWithHistoryContext } from '../context'
import Operation from './operation'
const Header = () => {
const {
appData,
currentConversationId,
currentConversationItem,
inputsForms,
pinnedConversationList,
handlePinConversation,
handleUnpinConversation,
conversationRenaming,
handleRenameConversation,
handleDeleteConversation,
handleNewConversation,
sidebarCollapseState,
handleSidebarCollapse,
isResponding,
} = useChatWithHistoryContext()
const { t } = useTranslation()
const isSidebarCollapsed = sidebarCollapseState
const isPin = pinnedConversationList.some((item) => item.id === currentConversationId)
const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null)
const [showRename, setShowRename] = useState<ConversationItem | null>(null)
const handleOperate = useCallback(
(type: string) => {
if (type === 'pin') handlePinConversation(currentConversationId)
if (type === 'unpin') handleUnpinConversation(currentConversationId)
if (type === 'delete') setShowConfirm(currentConversationItem as any)
if (type === 'rename') setShowRename(currentConversationItem as any)
},
[
currentConversationId,
currentConversationItem,
handlePinConversation,
handleUnpinConversation,
],
)
const handleCancelConfirm = useCallback(() => {
setShowConfirm(null)
}, [])
const handleDelete = useCallback(() => {
/* v8 ignore next -- defensive guard; onConfirm is only reachable when showConfirm is truthy. @preserve */
if (showConfirm) handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm })
}, [showConfirm, handleDeleteConversation, handleCancelConfirm])
const handleCancelRename = useCallback(() => {
setShowRename(null)
}, [])
const handleRename = useCallback(
(newName: string) => {
/* v8 ignore next -- defensive guard; onSave is only reachable when showRename is truthy. @preserve */
if (showRename)
handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
},
[showRename, handleRenameConversation, handleCancelRename],
)
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 system-md-semibold 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>
<TooltipTrigger
disabled={!!currentConversationId}
render={
<div>
<ActionButton
size="l"
state={
!currentConversationId || isResponding
? ActionButtonState.Disabled
: ActionButtonState.Default
}
disabled={!currentConversationId || isResponding}
onClick={handleNewConversation}
>
<RiEditBoxLine className="h-[18px] w-[18px]" />
</ActionButton>
</div>
}
/>
<TooltipContent>{t(($) => $['chat.newChatTip'], { ns: 'share' })}</TooltipContent>
</Tooltip>
)}
</div>
<div className="flex items-center gap-1">
{currentConversationId && (
<Tooltip>
<TooltipTrigger
render={
<ActionButton size="l" onClick={handleNewConversation}>
<RiResetLeftLine className="h-[18px] w-[18px]" />
</ActionButton>
}
/>
<TooltipContent>{t(($) => $['chat.resetChat'], { ns: 'share' })}</TooltipContent>
</Tooltip>
)}
{currentConversationId && inputsForms.length > 0 && <ViewFormDropdown />}
</div>
</div>
<AlertDialog open={!!showConfirm} onOpenChange={(open) => !open && handleCancelConfirm()}>
<AlertDialogContent>
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
<AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
{t(($) => $['chat.deleteConversation.title'], { ns: 'share' })}
</AlertDialogTitle>
<AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
{t(($) => $['chat.deleteConversation.content'], { ns: 'share' }) || ''}
</AlertDialogDescription>
</div>
<AlertDialogActions>
<AlertDialogCancelButton>
{t(($) => $['operation.cancel'], { ns: 'common' })}
</AlertDialogCancelButton>
<AlertDialogConfirmButton onClick={handleDelete}>
{t(($) => $['operation.confirm'], { ns: 'common' })}
</AlertDialogConfirmButton>
</AlertDialogActions>
</AlertDialogContent>
</AlertDialog>
{showRename && (
<RenameModal
isShow
onClose={handleCancelRename}
saveLoading={conversationRenaming}
name={showRename?.name || ''}
onSave={handleRename}
/>
)}
</>
)
}
export default Header