index.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { useCallback, useState } from 'react'
  2. import {
  3. RiEditBoxLine,
  4. RiLayoutRight2Line,
  5. RiResetLeftLine,
  6. } from '@remixicon/react'
  7. import { useTranslation } from 'react-i18next'
  8. import {
  9. useChatWithHistoryContext,
  10. } from '../context'
  11. import Operation from './operation'
  12. import ActionButton from '@/app/components/base/action-button'
  13. import AppIcon from '@/app/components/base/app-icon'
  14. import Tooltip from '@/app/components/base/tooltip'
  15. import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown'
  16. import Confirm from '@/app/components/base/confirm'
  17. import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
  18. import type { ConversationItem } from '@/models/share'
  19. import cn from '@/utils/classnames'
  20. const Header = () => {
  21. const {
  22. appData,
  23. currentConversationId,
  24. currentConversationItem,
  25. inputsForms,
  26. pinnedConversationList,
  27. handlePinConversation,
  28. handleUnpinConversation,
  29. conversationRenaming,
  30. handleRenameConversation,
  31. handleDeleteConversation,
  32. handleNewConversation,
  33. sidebarCollapseState,
  34. handleSidebarCollapse,
  35. } = useChatWithHistoryContext()
  36. const { t } = useTranslation()
  37. const isSidebarCollapsed = sidebarCollapseState
  38. const isPin = pinnedConversationList.some(item => item.id === currentConversationId)
  39. const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null)
  40. const [showRename, setShowRename] = useState<ConversationItem | null>(null)
  41. const handleOperate = useCallback((type: string) => {
  42. if (type === 'pin')
  43. handlePinConversation(currentConversationId)
  44. if (type === 'unpin')
  45. handleUnpinConversation(currentConversationId)
  46. if (type === 'delete')
  47. setShowConfirm(currentConversationItem as any)
  48. if (type === 'rename')
  49. setShowRename(currentConversationItem as any)
  50. }, [currentConversationId, currentConversationItem, handlePinConversation, handleUnpinConversation])
  51. const handleCancelConfirm = useCallback(() => {
  52. setShowConfirm(null)
  53. }, [])
  54. const handleDelete = useCallback(() => {
  55. if (showConfirm)
  56. handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm })
  57. }, [showConfirm, handleDeleteConversation, handleCancelConfirm])
  58. const handleCancelRename = useCallback(() => {
  59. setShowRename(null)
  60. }, [])
  61. const handleRename = useCallback((newName: string) => {
  62. if (showRename)
  63. handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
  64. }, [showRename, handleRenameConversation, handleCancelRename])
  65. return (
  66. <>
  67. <div className='shrink-0 h-14 p-3 flex items-center justify-between'>
  68. <div className={cn('flex items-center gap-1 transition-all duration-200 ease-in-out', !isSidebarCollapsed && 'opacity-0 user-select-none')}>
  69. <ActionButton className={cn(!isSidebarCollapsed && 'cursor-default')} size='l' onClick={() => handleSidebarCollapse(false)}>
  70. <RiLayoutRight2Line className='w-[18px] h-[18px]' />
  71. </ActionButton>
  72. <div className='shrink-0 mr-1'>
  73. <AppIcon
  74. size='large'
  75. iconType={appData?.site.icon_type}
  76. icon={appData?.site.icon}
  77. background={appData?.site.icon_background}
  78. imageUrl={appData?.site.icon_url}
  79. />
  80. </div>
  81. {!currentConversationId && (
  82. <div className={cn('grow text-text-secondary system-md-semibold truncate')}>{appData?.site.title}</div>
  83. )}
  84. {currentConversationId && currentConversationItem && isSidebarCollapsed && (
  85. <>
  86. <div className='p-1 text-divider-deep'>/</div>
  87. <Operation
  88. title={currentConversationItem?.name || ''}
  89. isPinned={!!isPin}
  90. togglePin={() => handleOperate(isPin ? 'unpin' : 'pin')}
  91. isShowDelete
  92. isShowRenameConversation
  93. onRenameConversation={() => handleOperate('rename')}
  94. onDelete={() => handleOperate('delete')}
  95. />
  96. </>
  97. )}
  98. <div className='px-1 flex items-center'>
  99. <div className='h-[14px] w-px bg-divider-regular'></div>
  100. </div>
  101. {isSidebarCollapsed && (
  102. <ActionButton size='l' onClick={handleNewConversation}>
  103. <RiEditBoxLine className='w-[18px] h-[18px]' />
  104. </ActionButton>
  105. )}
  106. </div>
  107. <div className='flex items-center gap-1'>
  108. {currentConversationId && (
  109. <Tooltip
  110. popupContent={t('share.chat.resetChat')}
  111. >
  112. <ActionButton size='l' onClick={handleNewConversation}>
  113. <RiResetLeftLine className='w-[18px] h-[18px]' />
  114. </ActionButton>
  115. </Tooltip>
  116. )}
  117. {currentConversationId && inputsForms.length > 0 && (
  118. <ViewFormDropdown />
  119. )}
  120. </div>
  121. </div>
  122. {!!showConfirm && (
  123. <Confirm
  124. title={t('share.chat.deleteConversation.title')}
  125. content={t('share.chat.deleteConversation.content') || ''}
  126. isShow
  127. onCancel={handleCancelConfirm}
  128. onConfirm={handleDelete}
  129. />
  130. )}
  131. {showRename && (
  132. <RenameModal
  133. isShow
  134. onClose={handleCancelRename}
  135. saveLoading={conversationRenaming}
  136. name={showRename?.name || ''}
  137. onSave={handleRename}
  138. />
  139. )}
  140. </>
  141. )
  142. }
  143. export default Header