index.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useRef } from 'react'
  4. import {
  5. ChatBubbleOvalLeftEllipsisIcon,
  6. } from '@heroicons/react/24/outline'
  7. import { useInfiniteScroll } from 'ahooks'
  8. import { ChatBubbleOvalLeftEllipsisIcon as ChatBubbleOvalLeftEllipsisSolidIcon } from '@heroicons/react/24/solid'
  9. import cn from 'classnames'
  10. import s from './style.module.css'
  11. import type { ConversationItem } from '@/models/share'
  12. import { fetchConversations } from '@/service/share'
  13. import { fetchConversations as fetchUniversalConversations } from '@/service/universal-chat'
  14. import ItemOperation from '@/app/components/explore/item-operation'
  15. export type IListProps = {
  16. className: string
  17. currentId: string
  18. onCurrentIdChange: (id: string) => void
  19. list: ConversationItem[]
  20. isClearConversationList: boolean
  21. isInstalledApp: boolean
  22. isUniversalChat?: boolean
  23. installedAppId?: string
  24. onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
  25. isNoMore: boolean
  26. isPinned: boolean
  27. onPinChanged: (id: string) => void
  28. controlUpdate: number
  29. onDelete: (id: string) => void
  30. }
  31. const List: FC<IListProps> = ({
  32. className,
  33. currentId,
  34. onCurrentIdChange,
  35. list,
  36. isClearConversationList,
  37. isInstalledApp,
  38. isUniversalChat,
  39. installedAppId,
  40. onMoreLoaded,
  41. isNoMore,
  42. isPinned,
  43. onPinChanged,
  44. controlUpdate,
  45. onDelete,
  46. }) => {
  47. const listRef = useRef<HTMLDivElement>(null)
  48. useInfiniteScroll(
  49. async () => {
  50. if (!isNoMore) {
  51. let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
  52. if (lastId === '-1')
  53. lastId = undefined
  54. let res: any
  55. if (isUniversalChat)
  56. res = await fetchUniversalConversations(lastId, isPinned)
  57. else
  58. res = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned)
  59. const { data: conversations, has_more }: any = res
  60. onMoreLoaded({ data: conversations, has_more })
  61. }
  62. return { list: [] }
  63. },
  64. {
  65. target: listRef,
  66. isNoMore: () => {
  67. return isNoMore
  68. },
  69. reloadDeps: [isNoMore, controlUpdate],
  70. },
  71. )
  72. return (
  73. <nav
  74. ref={listRef}
  75. className={cn(className, 'shrink-0 space-y-1 bg-white overflow-y-auto overflow-x-hidden')}
  76. >
  77. {list.map((item) => {
  78. const isCurrent = item.id === currentId
  79. const ItemIcon
  80. = isCurrent ? ChatBubbleOvalLeftEllipsisSolidIcon : ChatBubbleOvalLeftEllipsisIcon
  81. return (
  82. <div
  83. onClick={() => onCurrentIdChange(item.id)}
  84. key={item.id}
  85. className={cn(s.item,
  86. isCurrent
  87. ? 'bg-primary-50 text-primary-600'
  88. : 'text-gray-700 hover:bg-gray-200 hover:text-gray-700',
  89. 'group flex justify-between items-center rounded-md px-2 py-2 text-sm font-medium cursor-pointer',
  90. )}
  91. >
  92. <div className='flex items-center w-0 grow'>
  93. <ItemIcon
  94. className={cn(
  95. isCurrent
  96. ? 'text-primary-600'
  97. : 'text-gray-400 group-hover:text-gray-500',
  98. 'mr-3 h-5 w-5 flex-shrink-0',
  99. )}
  100. aria-hidden="true"
  101. />
  102. <span>{item.name}</span>
  103. </div>
  104. {item.id !== '-1' && (
  105. <div className={cn(s.opBtn, 'shrink-0')} onClick={e => e.stopPropagation()}>
  106. <ItemOperation
  107. isPinned={isPinned}
  108. togglePin={() => onPinChanged(item.id)}
  109. isShowDelete
  110. onDelete={() => onDelete(item.id)}
  111. />
  112. </div>
  113. )}
  114. </div>
  115. )
  116. })}
  117. </nav>
  118. )
  119. }
  120. export default React.memo(List)