index.tsx 5.2 KB

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