Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

index.tsx 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
  4. import { useDebounceFn } from 'ahooks'
  5. import { useTranslation } from 'react-i18next'
  6. import { createContext, useContext, useContextSelector } from 'use-context-selector'
  7. import { useDocumentContext } from '../index'
  8. import { ProcessStatus } from '../segment-add'
  9. import s from './style.module.css'
  10. import SegmentList from './segment-list'
  11. import DisplayToggle from './display-toggle'
  12. import BatchAction from './common/batch-action'
  13. import SegmentDetail from './segment-detail'
  14. import SegmentCard from './segment-card'
  15. import ChildSegmentList from './child-segment-list'
  16. import NewChildSegment from './new-child-segment'
  17. import FullScreenDrawer from './common/full-screen-drawer'
  18. import ChildSegmentDetail from './child-segment-detail'
  19. import StatusItem from './status-item'
  20. import Pagination from '@/app/components/base/pagination'
  21. import cn from '@/utils/classnames'
  22. import { formatNumber } from '@/utils/format'
  23. import Divider from '@/app/components/base/divider'
  24. import Input from '@/app/components/base/input'
  25. import { ToastContext } from '@/app/components/base/toast'
  26. import type { Item } from '@/app/components/base/select'
  27. import { SimpleSelect } from '@/app/components/base/select'
  28. import { type ChildChunkDetail, ChunkingMode, type SegmentDetailModel, type SegmentUpdater } from '@/models/datasets'
  29. import NewSegment from '@/app/components/datasets/documents/detail/new-segment'
  30. import { useEventEmitterContextContext } from '@/context/event-emitter'
  31. import Checkbox from '@/app/components/base/checkbox'
  32. import {
  33. useChildSegmentList,
  34. useChildSegmentListKey,
  35. useChunkListAllKey,
  36. useChunkListDisabledKey,
  37. useChunkListEnabledKey,
  38. useDeleteChildSegment,
  39. useDeleteSegment,
  40. useDisableSegment,
  41. useEnableSegment,
  42. useSegmentList,
  43. useSegmentListKey,
  44. useUpdateChildSegment,
  45. useUpdateSegment,
  46. } from '@/service/knowledge/use-segment'
  47. import { useInvalid } from '@/service/use-base'
  48. const DEFAULT_LIMIT = 10
  49. type CurrSegmentType = {
  50. segInfo?: SegmentDetailModel
  51. showModal: boolean
  52. isEditMode?: boolean
  53. }
  54. type CurrChildChunkType = {
  55. childChunkInfo?: ChildChunkDetail
  56. showModal: boolean
  57. }
  58. type SegmentListContextValue = {
  59. isCollapsed: boolean
  60. fullScreen: boolean
  61. toggleFullScreen: (fullscreen?: boolean) => void
  62. currSegment: CurrSegmentType
  63. currChildChunk: CurrChildChunkType
  64. }
  65. const SegmentListContext = createContext<SegmentListContextValue>({
  66. isCollapsed: true,
  67. fullScreen: false,
  68. toggleFullScreen: () => {},
  69. currSegment: { showModal: false },
  70. currChildChunk: { showModal: false },
  71. })
  72. export const useSegmentListContext = (selector: (value: SegmentListContextValue) => any) => {
  73. return useContextSelector(SegmentListContext, selector)
  74. }
  75. type ICompletedProps = {
  76. embeddingAvailable: boolean
  77. showNewSegmentModal: boolean
  78. onNewSegmentModalChange: (state: boolean) => void
  79. importStatus: ProcessStatus | string | undefined
  80. archived?: boolean
  81. }
  82. /**
  83. * Embedding done, show list of all segments
  84. * Support search and filter
  85. */
  86. const Completed: FC<ICompletedProps> = ({
  87. embeddingAvailable,
  88. showNewSegmentModal,
  89. onNewSegmentModalChange,
  90. importStatus,
  91. archived,
  92. }) => {
  93. const { t } = useTranslation()
  94. const { notify } = useContext(ToastContext)
  95. const datasetId = useDocumentContext(s => s.datasetId) || ''
  96. const documentId = useDocumentContext(s => s.documentId) || ''
  97. const docForm = useDocumentContext(s => s.docForm)
  98. const mode = useDocumentContext(s => s.mode)
  99. const parentMode = useDocumentContext(s => s.parentMode)
  100. // the current segment id and whether to show the modal
  101. const [currSegment, setCurrSegment] = useState<CurrSegmentType>({ showModal: false })
  102. const [currChildChunk, setCurrChildChunk] = useState<CurrChildChunkType>({ showModal: false })
  103. const [currChunkId, setCurrChunkId] = useState('')
  104. const [inputValue, setInputValue] = useState<string>('') // the input value
  105. const [searchValue, setSearchValue] = useState<string>('') // the search value
  106. const [selectedStatus, setSelectedStatus] = useState<boolean | 'all'>('all') // the selected status, enabled/disabled/undefined
  107. const [segments, setSegments] = useState<SegmentDetailModel[]>([]) // all segments data
  108. const [childSegments, setChildSegments] = useState<ChildChunkDetail[]>([]) // all child segments data
  109. const [selectedSegmentIds, setSelectedSegmentIds] = useState<string[]>([])
  110. const { eventEmitter } = useEventEmitterContextContext()
  111. const [isCollapsed, setIsCollapsed] = useState(true)
  112. const [currentPage, setCurrentPage] = useState(1) // start from 1
  113. const [limit, setLimit] = useState(DEFAULT_LIMIT)
  114. const [fullScreen, setFullScreen] = useState(false)
  115. const [showNewChildSegmentModal, setShowNewChildSegmentModal] = useState(false)
  116. const segmentListRef = useRef<HTMLDivElement>(null)
  117. const childSegmentListRef = useRef<HTMLDivElement>(null)
  118. const needScrollToBottom = useRef(false)
  119. const statusList = useRef<Item[]>([
  120. { value: 'all', name: t('datasetDocuments.list.index.all') },
  121. { value: 0, name: t('datasetDocuments.list.status.disabled') },
  122. { value: 1, name: t('datasetDocuments.list.status.enabled') },
  123. ])
  124. const { run: handleSearch } = useDebounceFn(() => {
  125. setSearchValue(inputValue)
  126. setCurrentPage(1)
  127. }, { wait: 500 })
  128. const handleInputChange = (value: string) => {
  129. setInputValue(value)
  130. handleSearch()
  131. }
  132. const onChangeStatus = ({ value }: Item) => {
  133. setSelectedStatus(value === 'all' ? 'all' : !!value)
  134. setCurrentPage(1)
  135. }
  136. const isFullDocMode = useMemo(() => {
  137. return mode === 'hierarchical' && parentMode === 'full-doc'
  138. }, [mode, parentMode])
  139. const { isFetching: isLoadingSegmentList, data: segmentListData } = useSegmentList(
  140. {
  141. datasetId,
  142. documentId,
  143. params: {
  144. page: isFullDocMode ? 1 : currentPage,
  145. limit: isFullDocMode ? 10 : limit,
  146. keyword: isFullDocMode ? '' : searchValue,
  147. enabled: selectedStatus,
  148. },
  149. },
  150. )
  151. const invalidSegmentList = useInvalid(useSegmentListKey)
  152. useEffect(() => {
  153. if (segmentListData) {
  154. setSegments(segmentListData.data || [])
  155. const totalPages = segmentListData.total_pages
  156. if (totalPages < currentPage)
  157. setCurrentPage(totalPages === 0 ? 1 : totalPages)
  158. }
  159. // eslint-disable-next-line react-hooks/exhaustive-deps
  160. }, [segmentListData])
  161. useEffect(() => {
  162. if (segmentListRef.current && needScrollToBottom.current) {
  163. segmentListRef.current.scrollTo({ top: segmentListRef.current.scrollHeight, behavior: 'smooth' })
  164. needScrollToBottom.current = false
  165. }
  166. }, [segments])
  167. const { isFetching: isLoadingChildSegmentList, data: childChunkListData } = useChildSegmentList(
  168. {
  169. datasetId,
  170. documentId,
  171. segmentId: segments[0]?.id || '',
  172. params: {
  173. page: currentPage === 0 ? 1 : currentPage,
  174. limit,
  175. keyword: searchValue,
  176. },
  177. },
  178. !isFullDocMode || segments.length === 0,
  179. )
  180. const invalidChildSegmentList = useInvalid(useChildSegmentListKey)
  181. useEffect(() => {
  182. if (childSegmentListRef.current && needScrollToBottom.current) {
  183. childSegmentListRef.current.scrollTo({ top: childSegmentListRef.current.scrollHeight, behavior: 'smooth' })
  184. needScrollToBottom.current = false
  185. }
  186. }, [childSegments])
  187. useEffect(() => {
  188. if (childChunkListData) {
  189. setChildSegments(childChunkListData.data || [])
  190. const totalPages = childChunkListData.total_pages
  191. if (totalPages < currentPage)
  192. setCurrentPage(totalPages === 0 ? 1 : totalPages)
  193. }
  194. // eslint-disable-next-line react-hooks/exhaustive-deps
  195. }, [childChunkListData])
  196. const resetList = useCallback(() => {
  197. setSelectedSegmentIds([])
  198. invalidSegmentList()
  199. // eslint-disable-next-line react-hooks/exhaustive-deps
  200. }, [])
  201. const resetChildList = useCallback(() => {
  202. invalidChildSegmentList()
  203. // eslint-disable-next-line react-hooks/exhaustive-deps
  204. }, [])
  205. const onClickCard = (detail: SegmentDetailModel, isEditMode = false) => {
  206. setCurrSegment({ segInfo: detail, showModal: true, isEditMode })
  207. }
  208. const onCloseSegmentDetail = useCallback(() => {
  209. setCurrSegment({ showModal: false })
  210. setFullScreen(false)
  211. }, [])
  212. const onCloseNewSegmentModal = useCallback(() => {
  213. onNewSegmentModalChange(false)
  214. setFullScreen(false)
  215. }, [onNewSegmentModalChange])
  216. const onCloseNewChildChunkModal = useCallback(() => {
  217. setShowNewChildSegmentModal(false)
  218. setFullScreen(false)
  219. }, [])
  220. const { mutateAsync: enableSegment } = useEnableSegment()
  221. const { mutateAsync: disableSegment } = useDisableSegment()
  222. const invalidChunkListAll = useInvalid(useChunkListAllKey)
  223. const invalidChunkListEnabled = useInvalid(useChunkListEnabledKey)
  224. const invalidChunkListDisabled = useInvalid(useChunkListDisabledKey)
  225. const refreshChunkListWithStatusChanged = () => {
  226. switch (selectedStatus) {
  227. case 'all':
  228. invalidChunkListDisabled()
  229. invalidChunkListEnabled()
  230. break
  231. default:
  232. invalidSegmentList()
  233. }
  234. }
  235. const onChangeSwitch = useCallback(async (enable: boolean, segId?: string) => {
  236. const operationApi = enable ? enableSegment : disableSegment
  237. await operationApi({ datasetId, documentId, segmentIds: segId ? [segId] : selectedSegmentIds }, {
  238. onSuccess: () => {
  239. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  240. for (const seg of segments) {
  241. if (segId ? seg.id === segId : selectedSegmentIds.includes(seg.id))
  242. seg.enabled = enable
  243. }
  244. setSegments([...segments])
  245. refreshChunkListWithStatusChanged()
  246. },
  247. onError: () => {
  248. notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  249. },
  250. })
  251. // eslint-disable-next-line react-hooks/exhaustive-deps
  252. }, [datasetId, documentId, selectedSegmentIds, segments])
  253. const { mutateAsync: deleteSegment } = useDeleteSegment()
  254. const onDelete = useCallback(async (segId?: string) => {
  255. await deleteSegment({ datasetId, documentId, segmentIds: segId ? [segId] : selectedSegmentIds }, {
  256. onSuccess: () => {
  257. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  258. resetList()
  259. !segId && setSelectedSegmentIds([])
  260. },
  261. onError: () => {
  262. notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  263. },
  264. })
  265. // eslint-disable-next-line react-hooks/exhaustive-deps
  266. }, [datasetId, documentId, selectedSegmentIds])
  267. const { mutateAsync: updateSegment } = useUpdateSegment()
  268. const refreshChunkListDataWithDetailChanged = () => {
  269. switch (selectedStatus) {
  270. case 'all':
  271. invalidChunkListDisabled()
  272. invalidChunkListEnabled()
  273. break
  274. case true:
  275. invalidChunkListAll()
  276. invalidChunkListDisabled()
  277. break
  278. case false:
  279. invalidChunkListAll()
  280. invalidChunkListEnabled()
  281. break
  282. }
  283. }
  284. const handleUpdateSegment = useCallback(async (
  285. segmentId: string,
  286. question: string,
  287. answer: string,
  288. keywords: string[],
  289. needRegenerate = false,
  290. ) => {
  291. const params: SegmentUpdater = { content: '' }
  292. if (docForm === ChunkingMode.qa) {
  293. if (!question.trim())
  294. return notify({ type: 'error', message: t('datasetDocuments.segment.questionEmpty') })
  295. if (!answer.trim())
  296. return notify({ type: 'error', message: t('datasetDocuments.segment.answerEmpty') })
  297. params.content = question
  298. params.answer = answer
  299. }
  300. else {
  301. if (!question.trim())
  302. return notify({ type: 'error', message: t('datasetDocuments.segment.contentEmpty') })
  303. params.content = question
  304. }
  305. if (keywords.length)
  306. params.keywords = keywords
  307. if (needRegenerate)
  308. params.regenerate_child_chunks = needRegenerate
  309. eventEmitter?.emit('update-segment')
  310. await updateSegment({ datasetId, documentId, segmentId, body: params }, {
  311. onSuccess(res) {
  312. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  313. if (!needRegenerate)
  314. onCloseSegmentDetail()
  315. for (const seg of segments) {
  316. if (seg.id === segmentId) {
  317. seg.answer = res.data.answer
  318. seg.content = res.data.content
  319. seg.keywords = res.data.keywords
  320. seg.word_count = res.data.word_count
  321. seg.hit_count = res.data.hit_count
  322. seg.enabled = res.data.enabled
  323. seg.updated_at = res.data.updated_at
  324. seg.child_chunks = res.data.child_chunks
  325. }
  326. }
  327. setSegments([...segments])
  328. refreshChunkListDataWithDetailChanged()
  329. eventEmitter?.emit('update-segment-success')
  330. },
  331. onSettled() {
  332. eventEmitter?.emit('update-segment-done')
  333. },
  334. })
  335. // eslint-disable-next-line react-hooks/exhaustive-deps
  336. }, [segments, datasetId, documentId])
  337. useEffect(() => {
  338. if (importStatus === ProcessStatus.COMPLETED)
  339. resetList()
  340. }, [importStatus, resetList])
  341. const onCancelBatchOperation = useCallback(() => {
  342. setSelectedSegmentIds([])
  343. }, [])
  344. const onSelected = useCallback((segId: string) => {
  345. setSelectedSegmentIds(prev =>
  346. prev.includes(segId)
  347. ? prev.filter(id => id !== segId)
  348. : [...prev, segId],
  349. )
  350. }, [])
  351. const isAllSelected = useMemo(() => {
  352. return segments.length > 0 && segments.every(seg => selectedSegmentIds.includes(seg.id))
  353. }, [segments, selectedSegmentIds])
  354. const isSomeSelected = useMemo(() => {
  355. return segments.some(seg => selectedSegmentIds.includes(seg.id))
  356. }, [segments, selectedSegmentIds])
  357. const onSelectedAll = useCallback(() => {
  358. setSelectedSegmentIds((prev) => {
  359. const currentAllSegIds = segments.map(seg => seg.id)
  360. const prevSelectedIds = prev.filter(item => !currentAllSegIds.includes(item))
  361. return [...prevSelectedIds, ...((isAllSelected || selectedSegmentIds.length > 0) ? [] : currentAllSegIds)]
  362. })
  363. }, [segments, isAllSelected, selectedSegmentIds])
  364. const totalText = useMemo(() => {
  365. const isSearch = searchValue !== '' || selectedStatus !== 'all'
  366. if (!isSearch) {
  367. const total = segmentListData?.total ? formatNumber(segmentListData.total) : '--'
  368. const count = total === '--' ? 0 : segmentListData!.total
  369. const translationKey = (mode === 'hierarchical' && parentMode === 'paragraph')
  370. ? 'datasetDocuments.segment.parentChunks'
  371. : 'datasetDocuments.segment.chunks'
  372. return `${total} ${t(translationKey, { count })}`
  373. }
  374. else {
  375. const total = typeof segmentListData?.total === 'number' ? formatNumber(segmentListData.total) : 0
  376. const count = segmentListData?.total || 0
  377. return `${total} ${t('datasetDocuments.segment.searchResults', { count })}`
  378. }
  379. // eslint-disable-next-line react-hooks/exhaustive-deps
  380. }, [segmentListData?.total, mode, parentMode, searchValue, selectedStatus])
  381. const toggleFullScreen = useCallback(() => {
  382. setFullScreen(!fullScreen)
  383. }, [fullScreen])
  384. const viewNewlyAddedChunk = useCallback(async () => {
  385. const totalPages = segmentListData?.total_pages || 0
  386. const total = segmentListData?.total || 0
  387. const newPage = Math.ceil((total + 1) / limit)
  388. needScrollToBottom.current = true
  389. if (newPage > totalPages) {
  390. setCurrentPage(totalPages + 1)
  391. }
  392. else {
  393. resetList()
  394. currentPage !== totalPages && setCurrentPage(totalPages)
  395. }
  396. // eslint-disable-next-line react-hooks/exhaustive-deps
  397. }, [segmentListData, limit, currentPage])
  398. const { mutateAsync: deleteChildSegment } = useDeleteChildSegment()
  399. const onDeleteChildChunk = useCallback(async (segmentId: string, childChunkId: string) => {
  400. await deleteChildSegment(
  401. { datasetId, documentId, segmentId, childChunkId },
  402. {
  403. onSuccess: () => {
  404. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  405. if (parentMode === 'paragraph')
  406. resetList()
  407. else
  408. resetChildList()
  409. },
  410. onError: () => {
  411. notify({ type: 'error', message: t('common.actionMsg.modifiedUnsuccessfully') })
  412. },
  413. },
  414. )
  415. // eslint-disable-next-line react-hooks/exhaustive-deps
  416. }, [datasetId, documentId, parentMode])
  417. const handleAddNewChildChunk = useCallback((parentChunkId: string) => {
  418. setShowNewChildSegmentModal(true)
  419. setCurrChunkId(parentChunkId)
  420. }, [])
  421. const onSaveNewChildChunk = useCallback((newChildChunk?: ChildChunkDetail) => {
  422. if (parentMode === 'paragraph') {
  423. for (const seg of segments) {
  424. if (seg.id === currChunkId)
  425. seg.child_chunks?.push(newChildChunk!)
  426. }
  427. setSegments([...segments])
  428. refreshChunkListDataWithDetailChanged()
  429. }
  430. else {
  431. resetChildList()
  432. }
  433. // eslint-disable-next-line react-hooks/exhaustive-deps
  434. }, [parentMode, currChunkId, segments])
  435. const viewNewlyAddedChildChunk = useCallback(() => {
  436. const totalPages = childChunkListData?.total_pages || 0
  437. const total = childChunkListData?.total || 0
  438. const newPage = Math.ceil((total + 1) / limit)
  439. needScrollToBottom.current = true
  440. if (newPage > totalPages) {
  441. setCurrentPage(totalPages + 1)
  442. }
  443. else {
  444. resetChildList()
  445. currentPage !== totalPages && setCurrentPage(totalPages)
  446. }
  447. // eslint-disable-next-line react-hooks/exhaustive-deps
  448. }, [childChunkListData, limit, currentPage])
  449. const onClickSlice = useCallback((detail: ChildChunkDetail) => {
  450. setCurrChildChunk({ childChunkInfo: detail, showModal: true })
  451. setCurrChunkId(detail.segment_id)
  452. }, [])
  453. const onCloseChildSegmentDetail = useCallback(() => {
  454. setCurrChildChunk({ showModal: false })
  455. setFullScreen(false)
  456. }, [])
  457. const { mutateAsync: updateChildSegment } = useUpdateChildSegment()
  458. const handleUpdateChildChunk = useCallback(async (
  459. segmentId: string,
  460. childChunkId: string,
  461. content: string,
  462. ) => {
  463. const params: SegmentUpdater = { content: '' }
  464. if (!content.trim())
  465. return notify({ type: 'error', message: t('datasetDocuments.segment.contentEmpty') })
  466. params.content = content
  467. eventEmitter?.emit('update-child-segment')
  468. await updateChildSegment({ datasetId, documentId, segmentId, childChunkId, body: params }, {
  469. onSuccess: (res) => {
  470. notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
  471. onCloseChildSegmentDetail()
  472. if (parentMode === 'paragraph') {
  473. for (const seg of segments) {
  474. if (seg.id === segmentId) {
  475. for (const childSeg of seg.child_chunks!) {
  476. if (childSeg.id === childChunkId) {
  477. childSeg.content = res.data.content
  478. childSeg.type = res.data.type
  479. childSeg.word_count = res.data.word_count
  480. childSeg.updated_at = res.data.updated_at
  481. }
  482. }
  483. }
  484. }
  485. setSegments([...segments])
  486. refreshChunkListDataWithDetailChanged()
  487. }
  488. else {
  489. resetChildList()
  490. }
  491. },
  492. onSettled: () => {
  493. eventEmitter?.emit('update-child-segment-done')
  494. },
  495. })
  496. // eslint-disable-next-line react-hooks/exhaustive-deps
  497. }, [segments, childSegments, datasetId, documentId, parentMode])
  498. const onClearFilter = useCallback(() => {
  499. setInputValue('')
  500. setSearchValue('')
  501. setSelectedStatus('all')
  502. setCurrentPage(1)
  503. }, [])
  504. return (
  505. <SegmentListContext.Provider value={{
  506. isCollapsed,
  507. fullScreen,
  508. toggleFullScreen,
  509. currSegment,
  510. currChildChunk,
  511. }}>
  512. {/* Menu Bar */}
  513. {!isFullDocMode && <div className={s.docSearchWrapper}>
  514. <Checkbox
  515. className='shrink-0'
  516. checked={isAllSelected}
  517. mixed={!isAllSelected && isSomeSelected}
  518. onCheck={onSelectedAll}
  519. disabled={isLoadingSegmentList}
  520. />
  521. <div className={'system-sm-semibold-uppercase pl-5 text-text-secondary flex-1'}>{totalText}</div>
  522. <SimpleSelect
  523. onSelect={onChangeStatus}
  524. items={statusList.current}
  525. defaultValue={selectedStatus === 'all' ? 'all' : selectedStatus ? 1 : 0}
  526. className={s.select}
  527. wrapperClassName='h-fit mr-2'
  528. optionWrapClassName='w-[160px]'
  529. optionClassName='p-0'
  530. renderOption={({ item, selected }) => <StatusItem item={item} selected={selected} />}
  531. notClearable
  532. />
  533. <Input
  534. showLeftIcon
  535. showClearIcon
  536. wrapperClassName='!w-52'
  537. value={inputValue}
  538. onChange={e => handleInputChange(e.target.value)}
  539. onClear={() => handleInputChange('')}
  540. />
  541. <Divider type='vertical' className='h-3.5 mx-3' />
  542. <DisplayToggle isCollapsed={isCollapsed} toggleCollapsed={() => setIsCollapsed(!isCollapsed)} />
  543. </div>}
  544. {/* Segment list */}
  545. {
  546. isFullDocMode
  547. ? <div className={cn(
  548. 'flex flex-col grow overflow-x-hidden',
  549. (isLoadingSegmentList || isLoadingChildSegmentList) ? 'overflow-y-hidden' : 'overflow-y-auto',
  550. )}>
  551. <SegmentCard
  552. detail={segments[0]}
  553. onClick={() => onClickCard(segments[0])}
  554. loading={isLoadingSegmentList}
  555. focused={{
  556. segmentIndex: currSegment?.segInfo?.id === segments[0]?.id,
  557. segmentContent: currSegment?.segInfo?.id === segments[0]?.id,
  558. }}
  559. />
  560. <ChildSegmentList
  561. parentChunkId={segments[0]?.id}
  562. onDelete={onDeleteChildChunk}
  563. childChunks={childSegments}
  564. handleInputChange={handleInputChange}
  565. handleAddNewChildChunk={handleAddNewChildChunk}
  566. onClickSlice={onClickSlice}
  567. enabled={!archived}
  568. total={childChunkListData?.total || 0}
  569. inputValue={inputValue}
  570. onClearFilter={onClearFilter}
  571. isLoading={isLoadingSegmentList || isLoadingChildSegmentList}
  572. />
  573. </div>
  574. : <SegmentList
  575. ref={segmentListRef}
  576. embeddingAvailable={embeddingAvailable}
  577. isLoading={isLoadingSegmentList}
  578. items={segments}
  579. selectedSegmentIds={selectedSegmentIds}
  580. onSelected={onSelected}
  581. onChangeSwitch={onChangeSwitch}
  582. onDelete={onDelete}
  583. onClick={onClickCard}
  584. archived={archived}
  585. onDeleteChildChunk={onDeleteChildChunk}
  586. handleAddNewChildChunk={handleAddNewChildChunk}
  587. onClickSlice={onClickSlice}
  588. onClearFilter={onClearFilter}
  589. />
  590. }
  591. {/* Pagination */}
  592. <Divider type='horizontal' className='w-auto h-[1px] my-0 mx-6 bg-divider-subtle' />
  593. <Pagination
  594. current={currentPage - 1}
  595. onChange={cur => setCurrentPage(cur + 1)}
  596. total={(isFullDocMode ? childChunkListData?.total : segmentListData?.total) || 0}
  597. limit={limit}
  598. onLimitChange={limit => setLimit(limit)}
  599. className={isFullDocMode ? 'px-3' : ''}
  600. />
  601. {/* Edit or view segment detail */}
  602. <FullScreenDrawer
  603. isOpen={currSegment.showModal}
  604. fullScreen={fullScreen}
  605. onClose={onCloseSegmentDetail}
  606. >
  607. <SegmentDetail
  608. segInfo={currSegment.segInfo ?? { id: '' }}
  609. docForm={docForm}
  610. isEditMode={currSegment.isEditMode}
  611. onUpdate={handleUpdateSegment}
  612. onCancel={onCloseSegmentDetail}
  613. />
  614. </FullScreenDrawer>
  615. {/* Create New Segment */}
  616. <FullScreenDrawer
  617. isOpen={showNewSegmentModal}
  618. fullScreen={fullScreen}
  619. onClose={onCloseNewSegmentModal}
  620. >
  621. <NewSegment
  622. docForm={docForm}
  623. onCancel={onCloseNewSegmentModal}
  624. onSave={resetList}
  625. viewNewlyAddedChunk={viewNewlyAddedChunk}
  626. />
  627. </FullScreenDrawer>
  628. {/* Edit or view child segment detail */}
  629. <FullScreenDrawer
  630. isOpen={currChildChunk.showModal}
  631. fullScreen={fullScreen}
  632. onClose={onCloseChildSegmentDetail}
  633. >
  634. <ChildSegmentDetail
  635. chunkId={currChunkId}
  636. childChunkInfo={currChildChunk.childChunkInfo ?? { id: '' }}
  637. docForm={docForm}
  638. onUpdate={handleUpdateChildChunk}
  639. onCancel={onCloseChildSegmentDetail}
  640. />
  641. </FullScreenDrawer>
  642. {/* Create New Child Segment */}
  643. <FullScreenDrawer
  644. isOpen={showNewChildSegmentModal}
  645. fullScreen={fullScreen}
  646. onClose={onCloseNewChildChunkModal}
  647. >
  648. <NewChildSegment
  649. chunkId={currChunkId}
  650. onCancel={onCloseNewChildChunkModal}
  651. onSave={onSaveNewChildChunk}
  652. viewNewlyAddedChildChunk={viewNewlyAddedChildChunk}
  653. />
  654. </FullScreenDrawer>
  655. {/* Batch Action Buttons */}
  656. {selectedSegmentIds.length > 0
  657. && <BatchAction
  658. className='absolute left-0 bottom-16 z-20'
  659. selectedIds={selectedSegmentIds}
  660. onBatchEnable={onChangeSwitch.bind(null, true, '')}
  661. onBatchDisable={onChangeSwitch.bind(null, false, '')}
  662. onBatchDelete={onDelete.bind(null, '')}
  663. onCancel={onCancelBatchOperation}
  664. />}
  665. </SegmentListContext.Provider>
  666. )
  667. }
  668. export default Completed