You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.tsx 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
  2. import Header from './header'
  3. import { useCallback, useEffect, useMemo, useState } from 'react'
  4. import FileList from './file-list'
  5. import type { OnlineDriveFile } from '@/models/pipeline'
  6. import { DatasourceType, OnlineDriveFileType } from '@/models/pipeline'
  7. import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
  8. import { ssePost } from '@/service/base'
  9. import type { DataSourceNodeCompletedResponse, DataSourceNodeErrorResponse } from '@/types/pipeline'
  10. import Toast from '@/app/components/base/toast'
  11. import { useDataSourceStore, useDataSourceStoreWithSelector } from '../store'
  12. import { convertOnlineDriveData } from './utils'
  13. import produce from 'immer'
  14. import { useShallow } from 'zustand/react/shallow'
  15. type OnlineDriveProps = {
  16. nodeId: string
  17. nodeData: DataSourceNodeType
  18. isInPipeline?: boolean
  19. }
  20. const OnlineDrive = ({
  21. nodeId,
  22. nodeData,
  23. isInPipeline = false,
  24. }: OnlineDriveProps) => {
  25. const pipelineId = useDatasetDetailContextWithSelector(s => s.dataset?.pipeline_id)
  26. const {
  27. prefix,
  28. keywords,
  29. bucket,
  30. selectedFileKeys,
  31. fileList,
  32. } = useDataSourceStoreWithSelector(useShallow(state => ({
  33. prefix: state.prefix,
  34. keywords: state.keywords,
  35. bucket: state.bucket,
  36. selectedFileKeys: state.selectedFileKeys,
  37. fileList: state.fileList,
  38. })))
  39. const dataSourceStore = useDataSourceStore()
  40. const [isLoading, setIsLoading] = useState(false)
  41. const datasourceNodeRunURL = !isInPipeline
  42. ? `/rag/pipelines/${pipelineId}/workflows/published/datasource/nodes/${nodeId}/run`
  43. : `/rag/pipelines/${pipelineId}/workflows/draft/datasource/nodes/${nodeId}/run`
  44. const getOnlineDriveFiles = useCallback(async (params: {
  45. prefix?: string[]
  46. bucket?: string
  47. startAfter?: string
  48. fileList?: OnlineDriveFile[]
  49. }) => {
  50. const { startAfter, prefix, bucket, fileList } = dataSourceStore.getState()
  51. const _prefix = params.prefix ?? prefix
  52. const _bucket = params.bucket ?? bucket
  53. const _startAfter = params.startAfter ?? startAfter.current
  54. const _fileList = params.fileList ?? fileList
  55. const prefixString = _prefix.length > 0 ? `${_prefix.join('/')}/` : ''
  56. setIsLoading(true)
  57. ssePost(
  58. datasourceNodeRunURL,
  59. {
  60. body: {
  61. inputs: {
  62. prefix: prefixString,
  63. bucket: _bucket,
  64. start_after: _startAfter,
  65. max_keys: 30, // Adjust as needed
  66. },
  67. datasource_type: DatasourceType.onlineDrive,
  68. },
  69. },
  70. {
  71. onDataSourceNodeCompleted: (documentsData: DataSourceNodeCompletedResponse) => {
  72. const { setFileList, isTruncated } = dataSourceStore.getState()
  73. const { fileList: newFileList, isTruncated: newIsTruncated } = convertOnlineDriveData(documentsData.data, _prefix, _bucket)
  74. setFileList([..._fileList, ...newFileList])
  75. isTruncated.current = newIsTruncated
  76. setIsLoading(false)
  77. },
  78. onDataSourceNodeError: (error: DataSourceNodeErrorResponse) => {
  79. Toast.notify({
  80. type: 'error',
  81. message: error.error,
  82. })
  83. setIsLoading(false)
  84. },
  85. },
  86. )
  87. }, [datasourceNodeRunURL, dataSourceStore])
  88. useEffect(() => {
  89. const {
  90. setFileList,
  91. setBucket,
  92. setPrefix,
  93. setKeywords,
  94. setSelectedFileKeys,
  95. currentNodeIdRef,
  96. } = dataSourceStore.getState()
  97. if (nodeId !== currentNodeIdRef.current) {
  98. setFileList([])
  99. setBucket('')
  100. setPrefix([])
  101. setKeywords('')
  102. setSelectedFileKeys([])
  103. currentNodeIdRef.current = nodeId
  104. getOnlineDriveFiles({
  105. prefix: [],
  106. bucket: '',
  107. fileList: [],
  108. startAfter: '',
  109. })
  110. }
  111. else {
  112. // Avoid fetching files when come back from next step
  113. if (fileList.length > 0) return
  114. getOnlineDriveFiles({})
  115. }
  116. // eslint-disable-next-line react-hooks/exhaustive-deps
  117. }, [nodeId])
  118. const onlineDriveFileList = useMemo(() => {
  119. if (keywords)
  120. return fileList.filter(file => file.key.toLowerCase().includes(keywords.toLowerCase()))
  121. return fileList
  122. }, [fileList, keywords])
  123. const updateKeywords = useCallback((keywords: string) => {
  124. const { setKeywords } = dataSourceStore.getState()
  125. setKeywords(keywords)
  126. }, [dataSourceStore])
  127. const resetKeywords = useCallback(() => {
  128. const { setKeywords } = dataSourceStore.getState()
  129. setKeywords('')
  130. }, [dataSourceStore])
  131. const handleSelectFile = useCallback((file: OnlineDriveFile) => {
  132. const { selectedFileKeys, setSelectedFileKeys } = dataSourceStore.getState()
  133. if (file.type === OnlineDriveFileType.bucket) return
  134. const newSelectedFileList = produce(selectedFileKeys, (draft) => {
  135. if (draft.includes(file.key)) {
  136. const index = draft.indexOf(file.key)
  137. draft.splice(index, 1)
  138. }
  139. else {
  140. if (isInPipeline && draft.length >= 1) return
  141. draft.push(file.key)
  142. }
  143. })
  144. setSelectedFileKeys(newSelectedFileList)
  145. }, [dataSourceStore, isInPipeline])
  146. const handleOpenFolder = useCallback((file: OnlineDriveFile) => {
  147. const { prefix, setPrefix, setBucket, setFileList, setSelectedFileKeys } = dataSourceStore.getState()
  148. if (file.type === OnlineDriveFileType.file) return
  149. setFileList([])
  150. if (file.type === OnlineDriveFileType.bucket) {
  151. setBucket(file.displayName)
  152. getOnlineDriveFiles({ bucket: file.displayName, fileList: [] })
  153. }
  154. else {
  155. setSelectedFileKeys([])
  156. const displayName = file.displayName.endsWith('/') ? file.displayName.slice(0, -1) : file.displayName
  157. const newPrefix = produce(prefix, (draft) => {
  158. draft.push(displayName)
  159. })
  160. setPrefix(newPrefix)
  161. getOnlineDriveFiles({ prefix: newPrefix, fileList: [] })
  162. }
  163. }, [dataSourceStore, getOnlineDriveFiles])
  164. return (
  165. <div className='flex flex-col gap-y-2'>
  166. <Header
  167. docTitle='Online Drive Docs'
  168. docLink='https://docs.dify.ai/'
  169. />
  170. <FileList
  171. fileList={onlineDriveFileList}
  172. selectedFileKeys={selectedFileKeys}
  173. prefix={prefix}
  174. keywords={keywords}
  175. bucket={bucket}
  176. resetKeywords={resetKeywords}
  177. updateKeywords={updateKeywords}
  178. searchResultsLength={onlineDriveFileList.length}
  179. handleSelectFile={handleSelectFile}
  180. handleOpenFolder={handleOpenFolder}
  181. isInPipeline={isInPipeline}
  182. isLoading={isLoading}
  183. getOnlineDriveFiles={getOnlineDriveFiles}
  184. />
  185. </div>
  186. )
  187. }
  188. export default OnlineDrive