Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

index.tsx 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useMemo } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import { intersectionBy } from 'lodash-es'
  6. import { useContext } from 'use-context-selector'
  7. import produce from 'immer'
  8. import { v4 as uuid4 } from 'uuid'
  9. import { useFormattingChangedDispatcher } from '../debug/hooks'
  10. import FeaturePanel from '../base/feature-panel'
  11. import OperationBtn from '../base/operation-btn'
  12. import CardItem from './card-item/item'
  13. import ParamsConfig from './params-config'
  14. import ContextVar from './context-var'
  15. import ConfigContext from '@/context/debug-configuration'
  16. import { AppType } from '@/types/app'
  17. import type { DataSet } from '@/models/datasets'
  18. import {
  19. getMultipleRetrievalConfig,
  20. getSelectedDatasetsMode,
  21. } from '@/app/components/workflow/nodes/knowledge-retrieval/utils'
  22. import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
  23. import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
  24. import { useSelector as useAppContextSelector } from '@/context/app-context'
  25. import { hasEditPermissionForDataset } from '@/utils/permission'
  26. import MetadataFilter from '@/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter'
  27. import type {
  28. HandleAddCondition,
  29. HandleRemoveCondition,
  30. HandleToggleConditionLogicalOperator,
  31. HandleUpdateCondition,
  32. MetadataFilteringModeEnum,
  33. } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
  34. import {
  35. ComparisonOperator,
  36. LogicalOperator,
  37. MetadataFilteringVariableType,
  38. } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
  39. const DatasetConfig: FC = () => {
  40. const { t } = useTranslation()
  41. const userProfile = useAppContextSelector(s => s.userProfile)
  42. const {
  43. mode,
  44. dataSets: dataSet,
  45. setDataSets: setDataSet,
  46. modelConfig,
  47. setModelConfig,
  48. showSelectDataSet,
  49. isAgent,
  50. datasetConfigs,
  51. datasetConfigsRef,
  52. setDatasetConfigs,
  53. setRerankSettingModalOpen,
  54. } = useContext(ConfigContext)
  55. const formattingChangedDispatcher = useFormattingChangedDispatcher()
  56. const hasData = dataSet.length > 0
  57. const {
  58. currentModel: currentRerankModel,
  59. currentProvider: currentRerankProvider,
  60. } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank)
  61. const onRemove = (id: string) => {
  62. const filteredDataSets = dataSet.filter(item => item.id !== id)
  63. setDataSet(filteredDataSets)
  64. const retrievalConfig = getMultipleRetrievalConfig(datasetConfigs as any, filteredDataSets, dataSet, {
  65. provider: currentRerankProvider?.provider,
  66. model: currentRerankModel?.model,
  67. })
  68. setDatasetConfigs({
  69. ...(datasetConfigs as any),
  70. ...retrievalConfig,
  71. })
  72. const {
  73. allExternal,
  74. allInternal,
  75. mixtureInternalAndExternal,
  76. mixtureHighQualityAndEconomic,
  77. inconsistentEmbeddingModel,
  78. } = getSelectedDatasetsMode(filteredDataSets)
  79. if (
  80. (allInternal && (mixtureHighQualityAndEconomic || inconsistentEmbeddingModel))
  81. || mixtureInternalAndExternal
  82. || allExternal
  83. )
  84. setRerankSettingModalOpen(true)
  85. formattingChangedDispatcher()
  86. }
  87. const handleSave = (newDataset: DataSet) => {
  88. const index = dataSet.findIndex(item => item.id === newDataset.id)
  89. const newDatasets = [...dataSet.slice(0, index), newDataset, ...dataSet.slice(index + 1)]
  90. setDataSet(newDatasets)
  91. formattingChangedDispatcher()
  92. }
  93. const promptVariables = modelConfig.configs.prompt_variables
  94. const promptVariablesToSelect = promptVariables.map(item => ({
  95. name: item.name,
  96. type: item.type,
  97. value: item.key,
  98. }))
  99. const selectedContextVar = promptVariables?.find(item => item.is_context_var)
  100. const handleSelectContextVar = (selectedValue: string) => {
  101. const newModelConfig = produce(modelConfig, (draft) => {
  102. draft.configs.prompt_variables = modelConfig.configs.prompt_variables.map((item) => {
  103. return ({
  104. ...item,
  105. is_context_var: item.key === selectedValue,
  106. })
  107. })
  108. })
  109. setModelConfig(newModelConfig)
  110. }
  111. const formattedDataset = useMemo(() => {
  112. return dataSet.map((item) => {
  113. const datasetConfig = {
  114. createdBy: item.created_by,
  115. partialMemberList: item.partial_member_list || [],
  116. permission: item.permission,
  117. }
  118. return {
  119. ...item,
  120. editable: hasEditPermissionForDataset(userProfile?.id || '', datasetConfig),
  121. }
  122. })
  123. }, [dataSet, userProfile?.id])
  124. const metadataList = useMemo(() => {
  125. return intersectionBy(...formattedDataset.filter((dataset) => {
  126. return !!dataset.doc_metadata
  127. }).map((dataset) => {
  128. return dataset.doc_metadata!
  129. }), 'name')
  130. }, [formattedDataset])
  131. const handleMetadataFilterModeChange = useCallback((newMode: MetadataFilteringModeEnum) => {
  132. setDatasetConfigs(produce(datasetConfigsRef.current!, (draft) => {
  133. draft.metadata_filtering_mode = newMode
  134. }))
  135. }, [setDatasetConfigs, datasetConfigsRef])
  136. const handleAddCondition = useCallback<HandleAddCondition>(({ name, type }) => {
  137. let operator: ComparisonOperator = ComparisonOperator.is
  138. if (type === MetadataFilteringVariableType.number)
  139. operator = ComparisonOperator.equal
  140. const newCondition = {
  141. id: uuid4(),
  142. name,
  143. comparison_operator: operator,
  144. }
  145. const newInputs = produce(datasetConfigsRef.current!, (draft) => {
  146. if (draft.metadata_filtering_conditions) {
  147. draft.metadata_filtering_conditions.conditions.push(newCondition)
  148. }
  149. else {
  150. draft.metadata_filtering_conditions = {
  151. logical_operator: LogicalOperator.and,
  152. conditions: [newCondition],
  153. }
  154. }
  155. })
  156. setDatasetConfigs(newInputs)
  157. }, [setDatasetConfigs, datasetConfigsRef])
  158. const handleRemoveCondition = useCallback<HandleRemoveCondition>((id) => {
  159. const conditions = datasetConfigsRef.current!.metadata_filtering_conditions?.conditions || []
  160. const index = conditions.findIndex(c => c.id === id)
  161. const newInputs = produce(datasetConfigsRef.current!, (draft) => {
  162. if (index > -1)
  163. draft.metadata_filtering_conditions?.conditions.splice(index, 1)
  164. })
  165. setDatasetConfigs(newInputs)
  166. }, [setDatasetConfigs, datasetConfigsRef])
  167. const handleUpdateCondition = useCallback<HandleUpdateCondition>((id, newCondition) => {
  168. const conditions = datasetConfigsRef.current!.metadata_filtering_conditions?.conditions || []
  169. const index = conditions.findIndex(c => c.id === id)
  170. const newInputs = produce(datasetConfigsRef.current!, (draft) => {
  171. if (index > -1)
  172. draft.metadata_filtering_conditions!.conditions[index] = newCondition
  173. })
  174. setDatasetConfigs(newInputs)
  175. }, [setDatasetConfigs, datasetConfigsRef])
  176. const handleToggleConditionLogicalOperator = useCallback<HandleToggleConditionLogicalOperator>(() => {
  177. const oldLogicalOperator = datasetConfigsRef.current!.metadata_filtering_conditions?.logical_operator
  178. const newLogicalOperator = oldLogicalOperator === LogicalOperator.and ? LogicalOperator.or : LogicalOperator.and
  179. const newInputs = produce(datasetConfigsRef.current!, (draft) => {
  180. draft.metadata_filtering_conditions!.logical_operator = newLogicalOperator
  181. })
  182. setDatasetConfigs(newInputs)
  183. }, [setDatasetConfigs, datasetConfigsRef])
  184. const handleMetadataModelChange = useCallback((model: { provider: string; modelId: string; mode?: string }) => {
  185. const newInputs = produce(datasetConfigsRef.current!, (draft) => {
  186. draft.metadata_model_config = {
  187. provider: model.provider,
  188. name: model.modelId,
  189. mode: model.mode || 'chat',
  190. completion_params: draft.metadata_model_config?.completion_params || { temperature: 0.7 },
  191. }
  192. })
  193. setDatasetConfigs(newInputs)
  194. }, [setDatasetConfigs, datasetConfigsRef])
  195. const handleMetadataCompletionParamsChange = useCallback((newParams: Record<string, any>) => {
  196. const newInputs = produce(datasetConfigsRef.current!, (draft) => {
  197. draft.metadata_model_config = {
  198. ...draft.metadata_model_config!,
  199. completion_params: newParams,
  200. }
  201. })
  202. setDatasetConfigs(newInputs)
  203. }, [setDatasetConfigs, datasetConfigsRef])
  204. return (
  205. <FeaturePanel
  206. className='mt-2'
  207. title={t('appDebug.feature.dataSet.title')}
  208. headerRight={
  209. <div className='flex items-center gap-1'>
  210. {!isAgent && <ParamsConfig disabled={!hasData} selectedDatasets={dataSet} />}
  211. <OperationBtn type="add" onClick={showSelectDataSet} />
  212. </div>
  213. }
  214. hasHeaderBottomBorder={!hasData}
  215. noBodySpacing
  216. >
  217. {hasData
  218. ? (
  219. <div className='mt-1 flex flex-wrap justify-between px-3 pb-3'>
  220. {formattedDataset.map(item => (
  221. <CardItem
  222. key={item.id}
  223. config={item}
  224. onRemove={onRemove}
  225. onSave={handleSave}
  226. editable={item.editable}
  227. />
  228. ))}
  229. </div>
  230. )
  231. : (
  232. <div className='mt-1 px-3 pb-3'>
  233. <div className='pb-1 pt-2 text-xs text-text-tertiary'>{t('appDebug.feature.dataSet.noData')}</div>
  234. </div>
  235. )}
  236. <div className='border-t border-t-divider-subtle py-2'>
  237. <MetadataFilter
  238. metadataList={metadataList}
  239. selectedDatasetsLoaded
  240. metadataFilterMode={datasetConfigs.metadata_filtering_mode}
  241. metadataFilteringConditions={datasetConfigs.metadata_filtering_conditions}
  242. handleAddCondition={handleAddCondition}
  243. handleMetadataFilterModeChange={handleMetadataFilterModeChange}
  244. handleRemoveCondition={handleRemoveCondition}
  245. handleToggleConditionLogicalOperator={handleToggleConditionLogicalOperator}
  246. handleUpdateCondition={handleUpdateCondition}
  247. metadataModelConfig={datasetConfigs.metadata_model_config}
  248. handleMetadataModelChange={handleMetadataModelChange}
  249. handleMetadataCompletionParamsChange={handleMetadataCompletionParamsChange}
  250. isCommonVariable
  251. availableCommonStringVars={promptVariablesToSelect.filter(item => item.type === MetadataFilteringVariableType.string)}
  252. availableCommonNumberVars={promptVariablesToSelect.filter(item => item.type === MetadataFilteringVariableType.number)}
  253. />
  254. </div>
  255. {mode === AppType.completion && dataSet.length > 0 && (
  256. <ContextVar
  257. value={selectedContextVar?.key}
  258. options={promptVariablesToSelect}
  259. onChange={handleSelectContextVar}
  260. />
  261. )}
  262. </FeaturePanel>
  263. )
  264. }
  265. export default React.memo(DatasetConfig)