index.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import React, { useEffect, useState } from 'react'
  2. import type { FC } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import {
  5. PencilSquareIcon,
  6. } from '@heroicons/react/24/outline'
  7. import cn from 'classnames'
  8. import Button from '../../../base/button'
  9. import List from './list'
  10. import AppInfo from '@/app/components/share/chat/sidebar/app-info'
  11. // import Card from './card'
  12. import type { ConversationItem, SiteInfo } from '@/models/share'
  13. import { fetchConversations } from '@/service/share'
  14. export type ISidebarProps = {
  15. copyRight: string
  16. currentId: string
  17. onCurrentIdChange: (id: string) => void
  18. list: ConversationItem[]
  19. isClearConversationList: boolean
  20. pinnedList: ConversationItem[]
  21. isClearPinnedConversationList: boolean
  22. isInstalledApp: boolean
  23. installedAppId?: string
  24. siteInfo: SiteInfo
  25. onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
  26. onPinnedMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
  27. isNoMore: boolean
  28. isPinnedNoMore: boolean
  29. onPin: (id: string) => void
  30. onUnpin: (id: string) => void
  31. controlUpdateList: number
  32. onDelete: (id: string) => void
  33. }
  34. const Sidebar: FC<ISidebarProps> = ({
  35. copyRight,
  36. currentId,
  37. onCurrentIdChange,
  38. list,
  39. isClearConversationList,
  40. pinnedList,
  41. isClearPinnedConversationList,
  42. isInstalledApp,
  43. installedAppId,
  44. siteInfo,
  45. onMoreLoaded,
  46. onPinnedMoreLoaded,
  47. isNoMore,
  48. isPinnedNoMore,
  49. onPin,
  50. onUnpin,
  51. controlUpdateList,
  52. onDelete,
  53. }) => {
  54. const { t } = useTranslation()
  55. const [hasPinned, setHasPinned] = useState(false)
  56. const checkHasPinned = async () => {
  57. const { data }: any = await fetchConversations(isInstalledApp, installedAppId, undefined, true)
  58. setHasPinned(data.length > 0)
  59. }
  60. useEffect(() => {
  61. checkHasPinned()
  62. }, [])
  63. useEffect(() => {
  64. if (controlUpdateList !== 0)
  65. checkHasPinned()
  66. }, [controlUpdateList])
  67. const maxListHeight = isInstalledApp ? 'max-h-[30vh]' : 'max-h-[40vh]'
  68. return (
  69. <div
  70. className={
  71. cn(
  72. isInstalledApp ? 'tablet:h-[calc(100vh_-_74px)]' : 'tablet:h-[calc(100vh_-_3rem)]',
  73. 'shrink-0 flex flex-col bg-white pc:w-[244px] tablet:w-[192px] mobile:w-[240px] border-r border-gray-200 mobile:h-screen',
  74. )
  75. }
  76. >
  77. {isInstalledApp && (
  78. <AppInfo
  79. className='my-4 px-4'
  80. name={siteInfo.title || ''}
  81. icon={siteInfo.icon || ''}
  82. icon_background={siteInfo.icon_background}
  83. />
  84. )}
  85. <div className="flex flex-shrink-0 p-4 !pb-0">
  86. <Button
  87. onClick={() => { onCurrentIdChange('-1') }}
  88. variant='secondary-accent'
  89. className="group w-full flex-shrink-0">
  90. <PencilSquareIcon className="mr-2 h-4 w-4" /> {t('share.chat.newChat')}
  91. </Button>
  92. </div>
  93. <div className={'flex-grow flex flex-col h-0 overflow-y-auto overflow-x-hidden'}>
  94. {/* pinned list */}
  95. {hasPinned && (
  96. <div className={cn('mt-4 px-4', list.length === 0 && 'flex flex-col flex-grow')}>
  97. <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.pinnedTitle')}</div>
  98. <List
  99. className={cn(list.length > 0 ? maxListHeight : 'flex-grow')}
  100. currentId={currentId}
  101. onCurrentIdChange={onCurrentIdChange}
  102. list={pinnedList}
  103. isClearConversationList={isClearPinnedConversationList}
  104. isInstalledApp={isInstalledApp}
  105. installedAppId={installedAppId}
  106. onMoreLoaded={onPinnedMoreLoaded}
  107. isNoMore={isPinnedNoMore}
  108. isPinned={true}
  109. onPinChanged={id => onUnpin(id)}
  110. controlUpdate={controlUpdateList + 1}
  111. onDelete={onDelete}
  112. />
  113. </div>
  114. )}
  115. {/* unpinned list */}
  116. <div className={cn('mt-4 px-4', !hasPinned && 'flex flex-col flex-grow')}>
  117. {(hasPinned && list.length > 0) && (
  118. <div className='mb-1.5 leading-[18px] text-xs text-gray-500 font-medium uppercase'>{t('share.chat.unpinnedTitle')}</div>
  119. )}
  120. <List
  121. className={cn(hasPinned ? maxListHeight : 'flex-grow')}
  122. currentId={currentId}
  123. onCurrentIdChange={onCurrentIdChange}
  124. list={list}
  125. isClearConversationList={isClearConversationList}
  126. isInstalledApp={isInstalledApp}
  127. installedAppId={installedAppId}
  128. onMoreLoaded={onMoreLoaded}
  129. isNoMore={isNoMore}
  130. isPinned={false}
  131. onPinChanged={id => onPin(id)}
  132. controlUpdate={controlUpdateList + 1}
  133. onDelete={onDelete}
  134. />
  135. </div>
  136. </div>
  137. <div className="flex flex-shrink-0 pr-4 pb-4 pl-4">
  138. <div className="text-gray-400 font-normal text-xs">© {copyRight} {(new Date()).getFullYear()}</div>
  139. </div>
  140. </div>
  141. )
  142. }
  143. export default React.memo(Sidebar)