index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useState } from 'react'
  4. import useSWR from 'swr'
  5. import { ArrowLeftIcon } from '@heroicons/react/24/solid'
  6. import { createContext } from 'use-context-selector'
  7. import { useTranslation } from 'react-i18next'
  8. import { useRouter } from 'next/navigation'
  9. import { omit } from 'lodash-es'
  10. import cn from 'classnames'
  11. import { OperationAction, StatusItem } from '../list'
  12. import s from '../style.module.css'
  13. import Completed from './completed'
  14. import Embedding from './embedding'
  15. import Metadata from './metadata'
  16. import style from './style.module.css'
  17. import Divider from '@/app/components/base/divider'
  18. import Loading from '@/app/components/base/loading'
  19. import type { MetadataType } from '@/service/datasets'
  20. import { fetchDocumentDetail } from '@/service/datasets'
  21. export const BackCircleBtn: FC<{ onClick: () => void }> = ({ onClick }) => {
  22. return (
  23. <div onClick={onClick} className={'rounded-full w-8 h-8 flex justify-center items-center border-gray-100 cursor-pointer border hover:border-gray-300 shadow-[0px_12px_16px_-4px_rgba(16,24,40,0.08),0px_4px_6px_-2px_rgba(16,24,40,0.03)]'}>
  24. <ArrowLeftIcon className='text-primary-600 fill-current stroke-current h-4 w-4' />
  25. </div>
  26. )
  27. }
  28. export const DocumentContext = createContext<{ datasetId?: string; documentId?: string; docForm: string }>({ docForm: '' })
  29. type DocumentTitleProps = {
  30. extension?: string
  31. name?: string
  32. iconCls?: string
  33. textCls?: string
  34. wrapperCls?: string
  35. }
  36. export const DocumentTitle: FC<DocumentTitleProps> = ({ extension, name, iconCls, textCls, wrapperCls }) => {
  37. const localExtension = extension?.toLowerCase() || name?.split('.')?.pop()?.toLowerCase()
  38. return <div className={cn('flex items-center justify-start flex-1', wrapperCls)}>
  39. <div className={cn(s[`${localExtension || 'txt'}Icon`], style.titleIcon, iconCls)}></div>
  40. <span className={cn('font-semibold text-lg text-gray-900 ml-1', textCls)}> {name || '--'}</span>
  41. </div>
  42. }
  43. type Props = {
  44. datasetId: string
  45. documentId: string
  46. }
  47. const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
  48. const { t } = useTranslation()
  49. const router = useRouter()
  50. const [showMetadata, setShowMetadata] = useState(true)
  51. const [showNewSegmentModal, setShowNewSegmentModal] = useState(false)
  52. const { data: documentDetail, error, mutate: detailMutate } = useSWR({
  53. action: 'fetchDocumentDetail',
  54. datasetId,
  55. documentId,
  56. params: { metadata: 'without' as MetadataType },
  57. }, apiParams => fetchDocumentDetail(omit(apiParams, 'action')))
  58. const { data: documentMetadata, error: metadataErr, mutate: metadataMutate } = useSWR({
  59. action: 'fetchDocumentDetail',
  60. datasetId,
  61. documentId,
  62. params: { metadata: 'only' as MetadataType },
  63. }, apiParams => fetchDocumentDetail(omit(apiParams, 'action')),
  64. )
  65. const backToPrev = () => {
  66. router.push(`/datasets/${datasetId}/documents`)
  67. }
  68. const isDetailLoading = !documentDetail && !error
  69. const isMetadataLoading = !documentMetadata && !metadataErr
  70. const embedding = ['queuing', 'indexing', 'paused'].includes((documentDetail?.display_status || '').toLowerCase())
  71. const handleOperate = (operateName?: string) => {
  72. if (operateName === 'delete')
  73. backToPrev()
  74. else
  75. detailMutate()
  76. }
  77. return (
  78. <DocumentContext.Provider value={{ datasetId, documentId, docForm: documentDetail?.doc_form || '' }}>
  79. <div className='flex flex-col h-full'>
  80. <div className='flex h-16 border-b-gray-100 border-b items-center p-4'>
  81. <BackCircleBtn onClick={backToPrev} />
  82. <Divider className='!h-4' type='vertical' />
  83. <DocumentTitle extension={documentDetail?.data_source_info?.upload_file?.extension} name={documentDetail?.name} />
  84. <StatusItem status={documentDetail?.display_status || 'available'} scene='detail' />
  85. <OperationAction
  86. scene='detail'
  87. detail={{
  88. enabled: documentDetail?.enabled || false,
  89. archived: documentDetail?.archived || false,
  90. id: documentId,
  91. doc_form: documentDetail?.doc_form || '',
  92. }}
  93. datasetId={datasetId}
  94. onUpdate={handleOperate}
  95. className='!w-[216px]'
  96. showNewSegmentModal={() => setShowNewSegmentModal(true)}
  97. />
  98. <button
  99. className={cn(style.layoutRightIcon, showMetadata ? style.iconShow : style.iconClose)}
  100. onClick={() => setShowMetadata(!showMetadata)}
  101. />
  102. </div>
  103. <div className='flex flex-row flex-1' style={{ height: 'calc(100% - 4rem)' }}>
  104. {isDetailLoading
  105. ? <Loading type='app' />
  106. : <div className={`box-border h-full w-full overflow-y-scroll ${embedding ? 'py-12 px-16' : 'pb-[30px] pt-3 px-6'}`}>
  107. {embedding
  108. ? <Embedding detail={documentDetail} detailUpdate={detailMutate} />
  109. : <Completed
  110. showNewSegmentModal={showNewSegmentModal}
  111. onNewSegmentModalChange={setShowNewSegmentModal}
  112. />
  113. }
  114. </div>
  115. }
  116. {showMetadata && <Metadata
  117. docDetail={{ ...documentDetail, ...documentMetadata } as any}
  118. loading={isMetadataLoading}
  119. onUpdate={metadataMutate}
  120. />}
  121. </div>
  122. </div>
  123. </DocumentContext.Provider>
  124. )
  125. }
  126. export default DocumentDetail