index.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useRef, useState } from 'react'
  4. import { useBoolean, useInfiniteScroll } from 'ahooks'
  5. import cn from 'classnames'
  6. import { useTranslation } from 'react-i18next'
  7. import RenameModal from '../rename-modal'
  8. import Item from './item'
  9. import type { ConversationItem } from '@/models/share'
  10. import { fetchConversations, renameConversation } from '@/service/share'
  11. import Toast from '@/app/components/base/toast'
  12. export type IListProps = {
  13. className: string
  14. currentId: string
  15. onCurrentIdChange: (id: string) => void
  16. list: ConversationItem[]
  17. onListChanged?: (newList: ConversationItem[]) => void
  18. isClearConversationList: boolean
  19. isInstalledApp: boolean
  20. installedAppId?: string
  21. onMoreLoaded: (res: { data: ConversationItem[]; has_more: boolean }) => void
  22. isNoMore: boolean
  23. isPinned: boolean
  24. onPinChanged: (id: string) => void
  25. controlUpdate: number
  26. onDelete: (id: string) => void
  27. }
  28. const List: FC<IListProps> = ({
  29. className,
  30. currentId,
  31. onCurrentIdChange,
  32. list,
  33. onListChanged,
  34. isClearConversationList,
  35. isInstalledApp,
  36. installedAppId,
  37. onMoreLoaded,
  38. isNoMore,
  39. isPinned,
  40. onPinChanged,
  41. controlUpdate,
  42. onDelete,
  43. }) => {
  44. const { t } = useTranslation()
  45. const listRef = useRef<HTMLDivElement>(null)
  46. useInfiniteScroll(
  47. async () => {
  48. if (!isNoMore) {
  49. let lastId = !isClearConversationList ? list[list.length - 1]?.id : undefined
  50. if (lastId === '-1')
  51. lastId = undefined
  52. const res = await fetchConversations(isInstalledApp, installedAppId, lastId, isPinned) as any
  53. const { data: conversations, has_more }: any = res
  54. onMoreLoaded({ data: conversations, has_more })
  55. }
  56. return { list: [] }
  57. },
  58. {
  59. target: listRef,
  60. isNoMore: () => {
  61. return isNoMore
  62. },
  63. reloadDeps: [isNoMore, controlUpdate],
  64. },
  65. )
  66. const [isShowRename, { setTrue: setShowRename, setFalse: setHideRename }] = useBoolean(false)
  67. const [isSaving, { setTrue: setIsSaving, setFalse: setNotSaving }] = useBoolean(false)
  68. const [currentConversation, setCurrentConversation] = useState<ConversationItem | null>(null)
  69. const showRename = (item: ConversationItem) => {
  70. setCurrentConversation(item)
  71. setShowRename()
  72. }
  73. const handleRename = async (newName: string) => {
  74. if (!newName.trim() || !currentConversation) {
  75. Toast.notify({
  76. type: 'error',
  77. message: t('common.chat.conversationNameCanNotEmpty'),
  78. })
  79. return
  80. }
  81. setIsSaving()
  82. const currId = currentConversation.id
  83. try {
  84. await renameConversation(isInstalledApp, installedAppId, currId, newName)
  85. Toast.notify({
  86. type: 'success',
  87. message: t('common.actionMsg.modifiedSuccessfully'),
  88. })
  89. onListChanged?.(list.map((item) => {
  90. if (item.id === currId) {
  91. return {
  92. ...item,
  93. name: newName,
  94. }
  95. }
  96. return item
  97. }))
  98. setHideRename()
  99. }
  100. finally {
  101. setNotSaving()
  102. }
  103. }
  104. return (
  105. <nav
  106. ref={listRef}
  107. className={cn(className, 'shrink-0 space-y-1 bg-white overflow-y-auto overflow-x-hidden')}
  108. >
  109. {list.map((item) => {
  110. const isCurrent = item.id === currentId
  111. return (
  112. <Item
  113. key={item.id}
  114. item={item}
  115. isCurrent={isCurrent}
  116. onClick={onCurrentIdChange}
  117. isPinned={isPinned}
  118. togglePin={onPinChanged}
  119. onDelete={onDelete}
  120. onRenameConversation={showRename}
  121. />
  122. )
  123. })}
  124. {isShowRename && (
  125. <RenameModal
  126. isShow={isShowRename}
  127. onClose={setHideRename}
  128. saveLoading={isSaving}
  129. name={currentConversation?.name || ''}
  130. onSave={handleRename}
  131. />
  132. )}
  133. </nav>
  134. )
  135. }
  136. export default React.memo(List)