header-in-mobile.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { useCallback, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import {
  4. RiMenuLine,
  5. } from '@remixicon/react'
  6. import { useChatWithHistoryContext } from './context'
  7. import Operation from './header/operation'
  8. import Sidebar from './sidebar'
  9. import MobileOperationDropdown from './header/mobile-operation-dropdown'
  10. import AppIcon from '@/app/components/base/app-icon'
  11. import ActionButton from '@/app/components/base/action-button'
  12. import { Message3Fill } from '@/app/components/base/icons/src/public/other'
  13. import InputsFormContent from '@/app/components/base/chat/chat-with-history/inputs-form/content'
  14. import Confirm from '@/app/components/base/confirm'
  15. import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
  16. import type { ConversationItem } from '@/models/share'
  17. const HeaderInMobile = () => {
  18. const {
  19. appData,
  20. currentConversationId,
  21. currentConversationItem,
  22. pinnedConversationList,
  23. handleNewConversation,
  24. handlePinConversation,
  25. handleUnpinConversation,
  26. handleDeleteConversation,
  27. handleRenameConversation,
  28. conversationRenaming,
  29. } = useChatWithHistoryContext()
  30. const { t } = useTranslation()
  31. const isPin = pinnedConversationList.some(item => item.id === currentConversationId)
  32. const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null)
  33. const [showRename, setShowRename] = useState<ConversationItem | null>(null)
  34. const handleOperate = useCallback((type: string) => {
  35. if (type === 'pin')
  36. handlePinConversation(currentConversationId)
  37. if (type === 'unpin')
  38. handleUnpinConversation(currentConversationId)
  39. if (type === 'delete')
  40. setShowConfirm(currentConversationItem as any)
  41. if (type === 'rename')
  42. setShowRename(currentConversationItem as any)
  43. }, [currentConversationId, currentConversationItem, handlePinConversation, handleUnpinConversation])
  44. const handleCancelConfirm = useCallback(() => {
  45. setShowConfirm(null)
  46. }, [])
  47. const handleDelete = useCallback(() => {
  48. if (showConfirm)
  49. handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm })
  50. }, [showConfirm, handleDeleteConversation, handleCancelConfirm])
  51. const handleCancelRename = useCallback(() => {
  52. setShowRename(null)
  53. }, [])
  54. const handleRename = useCallback((newName: string) => {
  55. if (showRename)
  56. handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
  57. }, [showRename, handleRenameConversation, handleCancelRename])
  58. const [showSidebar, setShowSidebar] = useState(false)
  59. const [showChatSettings, setShowChatSettings] = useState(false)
  60. return (
  61. <>
  62. <div className='shrink-0 flex items-center px-2 py-3 gap-1 bg-mask-top2bottom-gray-50-to-transparent'>
  63. <ActionButton size='l' className='shrink-0' onClick={() => setShowSidebar(true)}>
  64. <RiMenuLine className='w-[18px] h-[18px]' />
  65. </ActionButton>
  66. <div className='grow flex justify-center items-center'>
  67. {!currentConversationId && (
  68. <>
  69. <AppIcon
  70. className='mr-2'
  71. size='tiny'
  72. icon={appData?.site.icon}
  73. iconType={appData?.site.icon_type}
  74. imageUrl={appData?.site.icon_url}
  75. background={appData?.site.icon_background}
  76. />
  77. <div className='text-text-secondary system-md-semibold truncate'>
  78. {appData?.site.title}
  79. </div>
  80. </>
  81. )}
  82. {currentConversationId && (
  83. <Operation
  84. title={currentConversationItem?.name || ''}
  85. isPinned={!!isPin}
  86. togglePin={() => handleOperate(isPin ? 'unpin' : 'pin')}
  87. isShowDelete
  88. isShowRenameConversation
  89. onRenameConversation={() => handleOperate('rename')}
  90. onDelete={() => handleOperate('delete')}
  91. />
  92. )}
  93. </div>
  94. <MobileOperationDropdown
  95. handleResetChat={handleNewConversation}
  96. handleViewChatSettings={() => setShowChatSettings(true)}
  97. />
  98. </div>
  99. {showSidebar && (
  100. <div className='fixed inset-0 z-50 flex p-1 bg-background-overlay'
  101. onClick={() => setShowSidebar(false)}
  102. >
  103. <div className='flex h-full w-[calc(100vw_-_40px)] bg-components-panel-bg backdrop-blur-sm rounded-xl shadow-lg' onClick={e => e.stopPropagation()}>
  104. <Sidebar />
  105. </div>
  106. </div>
  107. )}
  108. {showChatSettings && (
  109. <div className='fixed inset-0 z-50 flex justify-end p-1 bg-background-overlay'
  110. onClick={() => setShowChatSettings(false)}
  111. >
  112. <div className='flex flex-col h-full w-[calc(100vw_-_40px)] bg-components-panel-bg backdrop-blur-sm rounded-xl shadow-lg' onClick={e => e.stopPropagation()}>
  113. <div className='flex items-center gap-3 px-4 py-3 rounded-t-2xl border-b border-divider-subtle'>
  114. <Message3Fill className='shrink-0 w-6 h-6' />
  115. <div className='grow text-text-secondary system-xl-semibold'>{t('share.chat.chatSettingsTitle')}</div>
  116. </div>
  117. <div className='p-4'>
  118. <InputsFormContent showTip />
  119. </div>
  120. </div>
  121. </div>
  122. )}
  123. {!!showConfirm && (
  124. <Confirm
  125. title={t('share.chat.deleteConversation.title')}
  126. content={t('share.chat.deleteConversation.content') || ''}
  127. isShow
  128. onCancel={handleCancelConfirm}
  129. onConfirm={handleDelete}
  130. />
  131. )}
  132. {showRename && (
  133. <RenameModal
  134. isShow
  135. onClose={handleCancelRename}
  136. saveLoading={conversationRenaming}
  137. name={showRename?.name || ''}
  138. onSave={handleRename}
  139. />
  140. )}
  141. </>
  142. )
  143. }
  144. export default HeaderInMobile