index.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import {
  2. useCallback,
  3. useState,
  4. } from 'react'
  5. import { useTranslation } from 'react-i18next'
  6. import {
  7. RiEditBoxLine,
  8. RiExpandRightLine,
  9. RiLayoutLeft2Line,
  10. } from '@remixicon/react'
  11. import { useChatWithHistoryContext } from '../context'
  12. import AppIcon from '@/app/components/base/app-icon'
  13. import ActionButton from '@/app/components/base/action-button'
  14. import Button from '@/app/components/base/button'
  15. import List from '@/app/components/base/chat/chat-with-history/sidebar/list'
  16. import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown'
  17. import Confirm from '@/app/components/base/confirm'
  18. import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
  19. import LogoSite from '@/app/components/base/logo/logo-site'
  20. import type { ConversationItem } from '@/models/share'
  21. import cn from '@/utils/classnames'
  22. type Props = {
  23. isPanel?: boolean
  24. }
  25. const Sidebar = ({ isPanel }: Props) => {
  26. const { t } = useTranslation()
  27. const {
  28. appData,
  29. handleNewConversation,
  30. pinnedConversationList,
  31. conversationList,
  32. currentConversationId,
  33. handleChangeConversation,
  34. handlePinConversation,
  35. handleUnpinConversation,
  36. conversationRenaming,
  37. handleRenameConversation,
  38. handleDeleteConversation,
  39. sidebarCollapseState,
  40. handleSidebarCollapse,
  41. isMobile,
  42. isResponding,
  43. } = useChatWithHistoryContext()
  44. const isSidebarCollapsed = sidebarCollapseState
  45. const [showConfirm, setShowConfirm] = useState<ConversationItem | null>(null)
  46. const [showRename, setShowRename] = useState<ConversationItem | null>(null)
  47. const handleOperate = useCallback((type: string, item: ConversationItem) => {
  48. if (type === 'pin')
  49. handlePinConversation(item.id)
  50. if (type === 'unpin')
  51. handleUnpinConversation(item.id)
  52. if (type === 'delete')
  53. setShowConfirm(item)
  54. if (type === 'rename')
  55. setShowRename(item)
  56. }, [handlePinConversation, handleUnpinConversation])
  57. const handleCancelConfirm = useCallback(() => {
  58. setShowConfirm(null)
  59. }, [])
  60. const handleDelete = useCallback(() => {
  61. if (showConfirm)
  62. handleDeleteConversation(showConfirm.id, { onSuccess: handleCancelConfirm })
  63. }, [showConfirm, handleDeleteConversation, handleCancelConfirm])
  64. const handleCancelRename = useCallback(() => {
  65. setShowRename(null)
  66. }, [])
  67. const handleRename = useCallback((newName: string) => {
  68. if (showRename)
  69. handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
  70. }, [showRename, handleRenameConversation, handleCancelRename])
  71. return (
  72. <div className={cn(
  73. 'flex w-full grow flex-col',
  74. isPanel && 'rounded-xl border-[0.5px] border-components-panel-border-subtle bg-components-panel-bg shadow-lg',
  75. )}>
  76. <div className={cn(
  77. 'flex shrink-0 items-center gap-3 p-3 pr-2',
  78. )}>
  79. <div className='shrink-0'>
  80. <AppIcon
  81. size='large'
  82. iconType={appData?.site.icon_type}
  83. icon={appData?.site.icon}
  84. background={appData?.site.icon_background}
  85. imageUrl={appData?.site.icon_url}
  86. />
  87. </div>
  88. <div className={cn('system-md-semibold grow truncate text-text-secondary')}>{appData?.site.title}</div>
  89. {!isMobile && isSidebarCollapsed && (
  90. <ActionButton size='l' onClick={() => handleSidebarCollapse(false)}>
  91. <RiExpandRightLine className='h-[18px] w-[18px]' />
  92. </ActionButton>
  93. )}
  94. {!isMobile && !isSidebarCollapsed && (
  95. <ActionButton size='l' onClick={() => handleSidebarCollapse(true)}>
  96. <RiLayoutLeft2Line className='h-[18px] w-[18px]' />
  97. </ActionButton>
  98. )}
  99. </div>
  100. <div className='shrink-0 px-3 py-4'>
  101. <Button variant='secondary-accent' disabled={isResponding} className='w-full justify-center' onClick={handleNewConversation}>
  102. <RiEditBoxLine className='mr-1 h-4 w-4' />
  103. {t('share.chat.newChat')}
  104. </Button>
  105. </div>
  106. <div className='h-0 grow space-y-2 overflow-y-auto px-3 pt-4'>
  107. {/* pinned list */}
  108. {!!pinnedConversationList.length && (
  109. <div className='mb-4'>
  110. <List
  111. isPin
  112. title={t('share.chat.pinnedTitle') || ''}
  113. list={pinnedConversationList}
  114. onChangeConversation={handleChangeConversation}
  115. onOperate={handleOperate}
  116. currentConversationId={currentConversationId}
  117. />
  118. </div>
  119. )}
  120. {!!conversationList.length && (
  121. <List
  122. title={(pinnedConversationList.length && t('share.chat.unpinnedTitle')) || ''}
  123. list={conversationList}
  124. onChangeConversation={handleChangeConversation}
  125. onOperate={handleOperate}
  126. currentConversationId={currentConversationId}
  127. />
  128. )}
  129. </div>
  130. <div className='flex shrink-0 items-center justify-between p-3'>
  131. <MenuDropdown placement='top-start' data={appData?.site} />
  132. {/* powered by */}
  133. <div className='shrink-0'>
  134. {!appData?.custom_config?.remove_webapp_brand && (
  135. <div className={cn(
  136. 'flex shrink-0 items-center gap-1.5 px-2',
  137. )}>
  138. <div className='system-2xs-medium-uppercase text-text-tertiary'>{t('share.chat.poweredBy')}</div>
  139. {appData?.custom_config?.replace_webapp_logo && (
  140. <img src={appData?.custom_config?.replace_webapp_logo} alt='logo' className='block h-5 w-auto' />
  141. )}
  142. {!appData?.custom_config?.replace_webapp_logo && (
  143. <LogoSite className='!h-5' />
  144. )}
  145. </div>
  146. )}
  147. </div>
  148. </div>
  149. {!!showConfirm && (
  150. <Confirm
  151. title={t('share.chat.deleteConversation.title')}
  152. content={t('share.chat.deleteConversation.content') || ''}
  153. isShow
  154. onCancel={handleCancelConfirm}
  155. onConfirm={handleDelete}
  156. />
  157. )}
  158. {showRename && (
  159. <RenameModal
  160. isShow
  161. onClose={handleCancelRename}
  162. saveLoading={conversationRenaming}
  163. name={showRename?.name || ''}
  164. onSave={handleRename}
  165. />
  166. )}
  167. </div>
  168. )
  169. }
  170. export default Sidebar