Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
  2. import Header from '../base/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. import { useModalContextSelector } from '@/context/modal-context'
  16. import { useGetDataSourceAuth } from '@/service/use-datasource'
  17. type OnlineDriveProps = {
  18. nodeId: string
  19. nodeData: DataSourceNodeType
  20. isInPipeline?: boolean
  21. onCredentialChange: (credentialId: string) => void
  22. }
  23. const OnlineDrive = ({
  24. nodeId,
  25. nodeData,
  26. isInPipeline = false,
  27. onCredentialChange,
  28. }: OnlineDriveProps) => {
  29. const [isInitialMount, setIsInitialMount] = useState(true)
  30. const pipelineId = useDatasetDetailContextWithSelector(s => s.dataset?.pipeline_id)
  31. const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal)
  32. const {
  33. nextPageParameters,
  34. prefix,
  35. keywords,
  36. bucket,
  37. selectedFileIds,
  38. fileList,
  39. currentCredentialId,
  40. } = useDataSourceStoreWithSelector(useShallow(state => ({
  41. nextPageParameters: state.nextPageParameters,
  42. prefix: state.prefix,
  43. keywords: state.keywords,
  44. bucket: state.bucket,
  45. selectedFileIds: state.selectedFileIds,
  46. fileList: state.fileList,
  47. currentCredentialId: state.currentCredentialId,
  48. })))
  49. const dataSourceStore = useDataSourceStore()
  50. const [isLoading, setIsLoading] = useState(false)
  51. const { data: dataSourceAuth } = useGetDataSourceAuth({
  52. pluginId: nodeData.plugin_id,
  53. provider: nodeData.provider_name,
  54. })
  55. const datasourceNodeRunURL = !isInPipeline
  56. ? `/rag/pipelines/${pipelineId}/workflows/published/datasource/nodes/${nodeId}/run`
  57. : `/rag/pipelines/${pipelineId}/workflows/draft/datasource/nodes/${nodeId}/run`
  58. const getOnlineDriveFiles = useCallback(async () => {
  59. const { nextPageParameters, prefix, bucket, fileList, currentCredentialId } = dataSourceStore.getState()
  60. const prefixString = prefix.length > 0 ? `${prefix.join('/')}/` : ''
  61. setIsLoading(true)
  62. ssePost(
  63. datasourceNodeRunURL,
  64. {
  65. body: {
  66. inputs: {
  67. prefix: prefixString,
  68. bucket,
  69. next_page_parameters: nextPageParameters,
  70. max_keys: 30, // Adjust as needed
  71. },
  72. datasource_type: DatasourceType.onlineDrive,
  73. credential_id: currentCredentialId,
  74. },
  75. },
  76. {
  77. onDataSourceNodeCompleted: (documentsData: DataSourceNodeCompletedResponse) => {
  78. const { setFileList, isTruncated, currentNextPageParametersRef } = dataSourceStore.getState()
  79. const {
  80. fileList: newFileList,
  81. isTruncated: newIsTruncated,
  82. nextPageParameters: newNextPageParameters,
  83. } = convertOnlineDriveData(documentsData.data, prefix, bucket)
  84. setFileList([...fileList, ...newFileList])
  85. isTruncated.current = newIsTruncated
  86. currentNextPageParametersRef.current = newNextPageParameters
  87. setIsLoading(false)
  88. },
  89. onDataSourceNodeError: (error: DataSourceNodeErrorResponse) => {
  90. Toast.notify({
  91. type: 'error',
  92. message: error.error,
  93. })
  94. setIsLoading(false)
  95. },
  96. },
  97. )
  98. }, [datasourceNodeRunURL, dataSourceStore])
  99. useEffect(() => {
  100. if (isInitialMount) {
  101. // Only fetch files on initial mount if fileList is empty
  102. if (fileList.length === 0)
  103. getOnlineDriveFiles()
  104. setIsInitialMount(false)
  105. }
  106. else {
  107. getOnlineDriveFiles()
  108. }
  109. }, [nextPageParameters, prefix, bucket, currentCredentialId])
  110. const onlineDriveFileList = useMemo(() => {
  111. if (keywords)
  112. return fileList.filter(file => file.name.toLowerCase().includes(keywords.toLowerCase()))
  113. return fileList
  114. }, [fileList, keywords])
  115. const updateKeywords = useCallback((keywords: string) => {
  116. const { setKeywords } = dataSourceStore.getState()
  117. setKeywords(keywords)
  118. }, [dataSourceStore])
  119. const resetKeywords = useCallback(() => {
  120. const { setKeywords } = dataSourceStore.getState()
  121. setKeywords('')
  122. }, [dataSourceStore])
  123. const handleSelectFile = useCallback((file: OnlineDriveFile) => {
  124. const { selectedFileIds, setSelectedFileIds } = dataSourceStore.getState()
  125. if (file.type === OnlineDriveFileType.bucket) return
  126. const newSelectedFileList = produce(selectedFileIds, (draft) => {
  127. if (draft.includes(file.id)) {
  128. const index = draft.indexOf(file.id)
  129. draft.splice(index, 1)
  130. }
  131. else {
  132. if (isInPipeline && draft.length >= 1) return
  133. draft.push(file.id)
  134. }
  135. })
  136. setSelectedFileIds(newSelectedFileList)
  137. }, [dataSourceStore, isInPipeline])
  138. const handleOpenFolder = useCallback((file: OnlineDriveFile) => {
  139. const { prefix, setPrefix, setBucket, setFileList, setSelectedFileIds } = dataSourceStore.getState()
  140. if (file.type === OnlineDriveFileType.file) return
  141. setFileList([])
  142. if (file.type === OnlineDriveFileType.bucket) {
  143. setBucket(file.name)
  144. }
  145. else {
  146. setSelectedFileIds([])
  147. const newPrefix = produce(prefix, (draft) => {
  148. draft.push(file.name)
  149. })
  150. setPrefix(newPrefix)
  151. }
  152. }, [dataSourceStore, getOnlineDriveFiles])
  153. const handleSetting = useCallback(() => {
  154. setShowAccountSettingModal({
  155. payload: 'data-source',
  156. })
  157. }, [setShowAccountSettingModal])
  158. return (
  159. <div className='flex flex-col gap-y-2'>
  160. <Header
  161. docTitle='Online Drive Docs'
  162. docLink='https://docs.dify.ai/'
  163. onClickConfiguration={handleSetting}
  164. pluginName={nodeData.datasource_label}
  165. currentCredentialId={currentCredentialId}
  166. onCredentialChange={onCredentialChange}
  167. credentials={dataSourceAuth?.result || []}
  168. />
  169. <FileList
  170. fileList={onlineDriveFileList}
  171. selectedFileIds={selectedFileIds}
  172. prefix={prefix}
  173. keywords={keywords}
  174. bucket={bucket}
  175. resetKeywords={resetKeywords}
  176. updateKeywords={updateKeywords}
  177. searchResultsLength={onlineDriveFileList.length}
  178. handleSelectFile={handleSelectFile}
  179. handleOpenFolder={handleOpenFolder}
  180. isInPipeline={isInPipeline}
  181. isLoading={isLoading}
  182. />
  183. </div>
  184. )
  185. }
  186. export default OnlineDrive