index.tsx 4.2 KB

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