index.tsx 5.6 KB

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