| @@ -1,18 +1,20 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import React, { useState } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { RiDeleteBinLine } from '@remixicon/react' | |||
| import { | |||
| RiDeleteBinLine, | |||
| RiEditLine, | |||
| } from '@remixicon/react' | |||
| import SettingsModal from '../settings-modal' | |||
| import type { DataSet } from '@/models/datasets' | |||
| import { DataSourceType } from '@/models/datasets' | |||
| import { formatNumber } from '@/utils/format' | |||
| import FileIcon from '@/app/components/base/file-icon' | |||
| import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' | |||
| import { Folder } from '@/app/components/base/icons/src/vender/solid/files' | |||
| import { Globe06 } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' | |||
| import Drawer from '@/app/components/base/drawer' | |||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | |||
| import Badge from '@/app/components/base/badge' | |||
| import { useKnowledge } from '@/hooks/use-knowledge' | |||
| type ItemProps = { | |||
| className?: string | |||
| @@ -27,12 +29,10 @@ const Item: FC<ItemProps> = ({ | |||
| onSave, | |||
| onRemove, | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| const media = useBreakpoints() | |||
| const isMobile = media === MediaType.mobile | |||
| const [showSettingsModal, setShowSettingsModal] = useState(false) | |||
| const { formatIndexingTechniqueAndMethod } = useKnowledge() | |||
| const handleSave = (newDataset: DataSet) => { | |||
| onSave(newDataset) | |||
| @@ -65,22 +65,17 @@ const Item: FC<ItemProps> = ({ | |||
| <div className='grow'> | |||
| <div className='flex items-center h-[18px]'> | |||
| <div className='grow text-[13px] font-medium text-gray-800 truncate' title={config.name}>{config.name}</div> | |||
| <div className='shrink-0 text-xs text-gray-500'> | |||
| {formatNumber(config.word_count)} {t('appDebug.feature.dataSet.words')} · {formatNumber(config.document_count)} {t('appDebug.feature.dataSet.textBlocks')} | |||
| </div> | |||
| <Badge | |||
| text={formatIndexingTechniqueAndMethod(config.indexing_technique, config.retrieval_model_dict?.search_method)} | |||
| /> | |||
| </div> | |||
| {/* { | |||
| config.description && ( | |||
| <div className='text-xs text-gray-500'>{config.description}</div> | |||
| ) | |||
| } */} | |||
| </div> | |||
| <div className='hidden rounded-lg group-hover:flex items-center justify-end absolute right-0 top-0 bottom-0 pr-2 w-[124px] bg-gradient-to-r from-white/50 to-white to-50%'> | |||
| <div | |||
| className='flex items-center justify-center mr-1 w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | |||
| onClick={() => setShowSettingsModal(true)} | |||
| > | |||
| <Settings01 className='w-4 h-4 text-gray-500' /> | |||
| <RiEditLine className='w-4 h-4 text-gray-500' /> | |||
| </div> | |||
| <div | |||
| className='group/action flex items-center justify-center w-6 h-6 hover:bg-[#FEE4E2] rounded-md cursor-pointer' | |||
| @@ -44,7 +44,8 @@ const DatasetConfig: FC = () => { | |||
| const handleSave = (newDataset: DataSet) => { | |||
| const index = dataSet.findIndex(item => item.id === newDataset.id) | |||
| setDataSet([...dataSet.slice(0, index), newDataset, ...dataSet.slice(index + 1)]) | |||
| const newDatasets = [...dataSet.slice(0, index), newDataset, ...dataSet.slice(index + 1)] | |||
| setDataSet(newDatasets) | |||
| formattingChangedDispatcher() | |||
| } | |||
| @@ -74,7 +75,7 @@ const DatasetConfig: FC = () => { | |||
| title={t('appDebug.feature.dataSet.title')} | |||
| headerRight={ | |||
| <div className='flex items-center gap-1'> | |||
| {!isAgent && <ParamsConfig />} | |||
| {!isAgent && <ParamsConfig disabled={!hasData} selectedDatasets={dataSet} />} | |||
| <OperationBtn type="add" onClick={showSelectDataSet} /> | |||
| </div> | |||
| } | |||
| @@ -1,10 +1,12 @@ | |||
| 'use client' | |||
| import React from 'react' | |||
| import { memo, useMemo } from 'react' | |||
| import type { FC } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { | |||
| RiQuestionLine, | |||
| } from '@remixicon/react' | |||
| import WeightedScore from './weighted-score' | |||
| import TopKItem from '@/app/components/base/param-item/top-k-item' | |||
| import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item' | |||
| import RadioCard from '@/app/components/base/radio-card/simple' | |||
| @@ -16,13 +18,20 @@ import { | |||
| import type { | |||
| DatasetConfigs, | |||
| } from '@/models/debug' | |||
| import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' | |||
| import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | |||
| import type { ModelConfig } from '@/app/components/workflow/types' | |||
| import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' | |||
| import TooltipPlus from '@/app/components/base/tooltip-plus' | |||
| import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import type { | |||
| DataSet, | |||
| WeightedScoreEnum, | |||
| } from '@/models/datasets' | |||
| import { RerankingModeEnum } from '@/models/datasets' | |||
| import cn from '@/utils/classnames' | |||
| import { useSelectedDatasetsMode } from '@/app/components/workflow/nodes/knowledge-retrieval/hooks' | |||
| import Switch from '@/app/components/base/switch' | |||
| type Props = { | |||
| datasetConfigs: DatasetConfigs | |||
| @@ -31,6 +40,7 @@ type Props = { | |||
| singleRetrievalModelConfig?: ModelConfig | |||
| onSingleRetrievalModelChange?: (config: ModelConfig) => void | |||
| onSingleRetrievalModelParamsChange?: (config: ModelConfig) => void | |||
| selectedDatasets?: DataSet[] | |||
| } | |||
| const ConfigContent: FC<Props> = ({ | |||
| @@ -40,8 +50,10 @@ const ConfigContent: FC<Props> = ({ | |||
| singleRetrievalModelConfig: singleRetrievalConfig = {} as ModelConfig, | |||
| onSingleRetrievalModelChange = () => { }, | |||
| onSingleRetrievalModelParamsChange = () => { }, | |||
| selectedDatasets = [], | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| const selectedDatasetsMode = useSelectedDatasetsMode(selectedDatasets) | |||
| const type = datasetConfigs.retrieval_model | |||
| const setType = (value: RETRIEVE_TYPE) => { | |||
| onChange({ | |||
| @@ -54,7 +66,7 @@ const ConfigContent: FC<Props> = ({ | |||
| defaultModel: rerankDefaultModel, | |||
| } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) | |||
| const rerankModel = (() => { | |||
| if (datasetConfigs.reranking_model) { | |||
| if (datasetConfigs.reranking_model?.reranking_provider_name) { | |||
| return { | |||
| provider_name: datasetConfigs.reranking_model.reranking_provider_name, | |||
| model_name: datasetConfigs.reranking_model.reranking_model_name, | |||
| @@ -93,14 +105,73 @@ const ConfigContent: FC<Props> = ({ | |||
| }) | |||
| } | |||
| const handleWeightedScoreChange = (value: { type: WeightedScoreEnum; value: number[] }) => { | |||
| const configs = { | |||
| ...datasetConfigs, | |||
| weights: { | |||
| ...datasetConfigs.weights!, | |||
| weight_type: value.type, | |||
| vector_setting: { | |||
| ...datasetConfigs.weights!.vector_setting!, | |||
| vector_weight: value.value[0], | |||
| }, | |||
| keyword_setting: { | |||
| keyword_weight: value.value[1], | |||
| }, | |||
| }, | |||
| } | |||
| onChange(configs) | |||
| } | |||
| const handleRerankModeChange = (mode: RerankingModeEnum) => { | |||
| onChange({ | |||
| ...datasetConfigs, | |||
| reranking_mode: mode, | |||
| }) | |||
| } | |||
| const model = singleRetrievalConfig | |||
| const rerankingModeOptions = [ | |||
| { | |||
| value: RerankingModeEnum.WeightedScore, | |||
| label: t('dataset.weightedScore.title'), | |||
| tips: t('dataset.weightedScore.description'), | |||
| }, | |||
| { | |||
| value: RerankingModeEnum.RerankingModel, | |||
| label: t('common.modelProvider.rerankModel.key'), | |||
| tips: t('common.modelProvider.rerankModel.tip'), | |||
| }, | |||
| ] | |||
| const showWeightedScore = selectedDatasetsMode.allHighQuality | |||
| && !selectedDatasetsMode.inconsistentEmbeddingModel | |||
| const showWeightedScorePanel = showWeightedScore && datasetConfigs.reranking_mode === RerankingModeEnum.WeightedScore && datasetConfigs.weights | |||
| const selectedRerankMode = datasetConfigs.reranking_mode || RerankingModeEnum.RerankingModel | |||
| const showRerankModel = useMemo(() => { | |||
| if (datasetConfigs.reranking_enable === false && selectedDatasetsMode.allEconomic) | |||
| return false | |||
| return true | |||
| }, [datasetConfigs.reranking_enable, selectedDatasetsMode.allEconomic]) | |||
| return ( | |||
| <div> | |||
| <div className='system-xl-semibold text-text-primary'>{t('dataset.retrievalSettings')}</div> | |||
| <div className='mt-2 space-y-3'> | |||
| <RadioCard | |||
| icon={<NTo1Retrieval className='shrink-0 mr-3 w-9 h-9 rounded-lg' />} | |||
| title={t('appDebug.datasetConfig.retrieveOneWay.title')} | |||
| title={( | |||
| <div className='flex items-center'> | |||
| {t('appDebug.datasetConfig.retrieveOneWay.title')} | |||
| <TooltipPlus popupContent={<div className='w-[320px]'>{t('dataset.nTo1RetrievalLegacy')}</div>}> | |||
| <div className='ml-1 flex items-center px-[5px] h-[18px] rounded-[5px] border border-text-accent-secondary system-2xs-medium-uppercase text-text-accent-secondary'>legacy</div> | |||
| </TooltipPlus> | |||
| </div> | |||
| )} | |||
| description={t('appDebug.datasetConfig.retrieveOneWay.description')} | |||
| isChosen={type === RETRIEVE_TYPE.oneWay} | |||
| onChosen={() => { setType(RETRIEVE_TYPE.oneWay) }} | |||
| @@ -115,43 +186,152 @@ const ConfigContent: FC<Props> = ({ | |||
| </div> | |||
| {type === RETRIEVE_TYPE.multiWay && ( | |||
| <> | |||
| <div className='mt-6'> | |||
| <div className='leading-[32px] text-[13px] font-medium text-gray-900'>{t('common.modelProvider.rerankModel.key')}</div> | |||
| <div> | |||
| <ModelSelector | |||
| defaultModel={rerankModel && { provider: rerankModel?.provider_name, model: rerankModel?.model_name }} | |||
| onSelect={(v) => { | |||
| onChange({ | |||
| ...datasetConfigs, | |||
| reranking_model: { | |||
| reranking_provider_name: v.provider, | |||
| reranking_model_name: v.model, | |||
| }, | |||
| }) | |||
| }} | |||
| modelList={rerankModelList} | |||
| /> | |||
| </div> | |||
| </div> | |||
| <div className='mt-4 space-y-4'> | |||
| <TopKItem | |||
| value={datasetConfigs.top_k} | |||
| onChange={handleParamChange} | |||
| enable={true} | |||
| /> | |||
| <ScoreThresholdItem | |||
| value={datasetConfigs.score_threshold as number} | |||
| onChange={handleParamChange} | |||
| enable={datasetConfigs.score_threshold_enabled} | |||
| hasSwitch={true} | |||
| onSwitchChange={handleSwitch} | |||
| /> | |||
| <div className='mb-2 mt-4 h-[1px] bg-divider-subtle'></div> | |||
| <div | |||
| className='flex items-center mb-2 h-6 system-md-semibold text-text-secondary' | |||
| > | |||
| {t('dataset.rerankSettings')} | |||
| </div> | |||
| { | |||
| selectedDatasetsMode.inconsistentEmbeddingModel | |||
| && ( | |||
| <div className='mt-4 system-xs-regular text-text-warning'> | |||
| {t('dataset.inconsistentEmbeddingModelTip')} | |||
| </div> | |||
| ) | |||
| } | |||
| { | |||
| selectedDatasetsMode.mixtureHighQualityAndEconomic | |||
| && ( | |||
| <div className='mt-4 system-xs-regular text-text-warning'> | |||
| {t('dataset.mixtureHighQualityAndEconomicTip')} | |||
| </div> | |||
| ) | |||
| } | |||
| { | |||
| showWeightedScore && ( | |||
| <div className='flex items-center justify-between'> | |||
| { | |||
| rerankingModeOptions.map(option => ( | |||
| <div | |||
| key={option.value} | |||
| className={cn( | |||
| 'flex items-center justify-center w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary', | |||
| selectedRerankMode === option.value && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary', | |||
| )} | |||
| onClick={() => handleRerankModeChange(option.value)} | |||
| > | |||
| <div className='truncate'>{option.label}</div> | |||
| <TooltipPlus | |||
| popupContent={<div className='w-[200px]'>{option.tips}</div>} | |||
| hideArrow | |||
| > | |||
| <RiQuestionLine className='ml-0.5 w-3.5 h-4.5 text-text-quaternary' /> | |||
| </TooltipPlus> | |||
| </div> | |||
| )) | |||
| } | |||
| </div> | |||
| ) | |||
| } | |||
| { | |||
| !showWeightedScorePanel && ( | |||
| <div className='mt-2'> | |||
| <div className='flex items-center'> | |||
| { | |||
| selectedDatasetsMode.allEconomic && ( | |||
| <Switch | |||
| size='md' | |||
| defaultValue={showRerankModel} | |||
| onChange={(v) => { | |||
| onChange({ | |||
| ...datasetConfigs, | |||
| reranking_enable: v, | |||
| }) | |||
| }} | |||
| /> | |||
| ) | |||
| } | |||
| <div className='ml-2 leading-[32px] text-[13px] font-medium text-gray-900'>{t('common.modelProvider.rerankModel.key')}</div> | |||
| <TooltipPlus popupContent={<div className="w-[200px]">{t('common.modelProvider.rerankModel.tip')}</div>}> | |||
| <RiQuestionLine className='ml-0.5 w-[14px] h-[14px] text-gray-400' /> | |||
| </TooltipPlus> | |||
| </div> | |||
| <div> | |||
| <ModelSelector | |||
| defaultModel={rerankModel && { provider: rerankModel?.provider_name, model: rerankModel?.model_name }} | |||
| onSelect={(v) => { | |||
| onChange({ | |||
| ...datasetConfigs, | |||
| reranking_model: { | |||
| reranking_provider_name: v.provider, | |||
| reranking_model_name: v.model, | |||
| }, | |||
| }) | |||
| }} | |||
| modelList={rerankModelList} | |||
| /> | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| { | |||
| showWeightedScorePanel | |||
| && ( | |||
| <div className='mt-2 space-y-4'> | |||
| <WeightedScore | |||
| value={{ | |||
| type: datasetConfigs.weights!.weight_type, | |||
| value: [ | |||
| datasetConfigs.weights!.vector_setting.vector_weight, | |||
| datasetConfigs.weights!.keyword_setting.keyword_weight, | |||
| ], | |||
| }} | |||
| onChange={handleWeightedScoreChange} | |||
| /> | |||
| <TopKItem | |||
| value={datasetConfigs.top_k} | |||
| onChange={handleParamChange} | |||
| enable={true} | |||
| /> | |||
| <ScoreThresholdItem | |||
| value={datasetConfigs.score_threshold as number} | |||
| onChange={handleParamChange} | |||
| enable={datasetConfigs.score_threshold_enabled} | |||
| hasSwitch={true} | |||
| onSwitchChange={handleSwitch} | |||
| /> | |||
| </div> | |||
| ) | |||
| } | |||
| { | |||
| !showWeightedScorePanel | |||
| && ( | |||
| <div className='mt-4 space-y-4'> | |||
| <TopKItem | |||
| value={datasetConfigs.top_k} | |||
| onChange={handleParamChange} | |||
| enable={true} | |||
| /> | |||
| { | |||
| showRerankModel && ( | |||
| <ScoreThresholdItem | |||
| value={datasetConfigs.score_threshold as number} | |||
| onChange={handleParamChange} | |||
| enable={datasetConfigs.score_threshold_enabled} | |||
| hasSwitch={true} | |||
| onSwitchChange={handleSwitch} | |||
| /> | |||
| ) | |||
| } | |||
| </div> | |||
| ) | |||
| } | |||
| </> | |||
| )} | |||
| {isInWorkflow && type === RETRIEVE_TYPE.oneWay && ( | |||
| <div className='mt-6'> | |||
| <div className='mt-4'> | |||
| <div className='flex items-center space-x-0.5'> | |||
| <div className='leading-[32px] text-[13px] font-medium text-gray-900'>{t('common.modelProvider.systemReasoningModel.key')}</div> | |||
| <TooltipPlus | |||
| @@ -180,4 +360,4 @@ const ConfigContent: FC<Props> = ({ | |||
| </div > | |||
| ) | |||
| } | |||
| export default React.memo(ConfigContent) | |||
| export default memo(ConfigContent) | |||
| @@ -1,29 +1,73 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import { memo, useState } from 'react' | |||
| import { memo, useEffect, useState } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { useContext } from 'use-context-selector' | |||
| import { RiEqualizer2Line } from '@remixicon/react' | |||
| import ConfigContent from './config-content' | |||
| import cn from '@/utils/classnames' | |||
| import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' | |||
| import ConfigContext from '@/context/debug-configuration' | |||
| import Modal from '@/app/components/base/modal' | |||
| import Button from '@/app/components/base/button' | |||
| import { RETRIEVE_TYPE } from '@/types/app' | |||
| import Toast from '@/app/components/base/toast' | |||
| import { DATASET_DEFAULT } from '@/config' | |||
| import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | |||
| import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import type { DataSet } from '@/models/datasets' | |||
| import type { DatasetConfigs } from '@/models/debug' | |||
| import { | |||
| getMultipleRetrievalConfig, | |||
| getSelectedDatasetsMode, | |||
| } from '@/app/components/workflow/nodes/knowledge-retrieval/utils' | |||
| const ParamsConfig: FC = () => { | |||
| type ParamsConfigProps = { | |||
| disabled?: boolean | |||
| selectedDatasets: DataSet[] | |||
| } | |||
| const ParamsConfig = ({ | |||
| disabled, | |||
| selectedDatasets, | |||
| }: ParamsConfigProps) => { | |||
| const { t } = useTranslation() | |||
| const [open, setOpen] = useState(false) | |||
| const { | |||
| datasetConfigs, | |||
| setDatasetConfigs, | |||
| rerankSettingModalOpen, | |||
| setRerankSettingModalOpen, | |||
| } = useContext(ConfigContext) | |||
| const [tempDataSetConfigs, setTempDataSetConfigs] = useState(datasetConfigs) | |||
| useEffect(() => { | |||
| const { | |||
| allEconomic, | |||
| } = getSelectedDatasetsMode(selectedDatasets) | |||
| const { datasets, retrieval_model, score_threshold_enabled, ...restConfigs } = datasetConfigs | |||
| let rerankEnable = restConfigs.reranking_enable | |||
| if (allEconomic && !restConfigs.reranking_model?.reranking_provider_name && rerankEnable === undefined) | |||
| rerankEnable = false | |||
| setTempDataSetConfigs({ | |||
| ...getMultipleRetrievalConfig({ | |||
| top_k: restConfigs.top_k, | |||
| score_threshold: restConfigs.score_threshold, | |||
| reranking_model: restConfigs.reranking_model && { | |||
| provider: restConfigs.reranking_model.reranking_provider_name, | |||
| model: restConfigs.reranking_model.reranking_model_name, | |||
| }, | |||
| reranking_mode: restConfigs.reranking_mode, | |||
| weights: restConfigs.weights, | |||
| reranking_enable: rerankEnable, | |||
| }, selectedDatasets), | |||
| reranking_model: restConfigs.reranking_model && { | |||
| reranking_provider_name: restConfigs.reranking_model.reranking_provider_name, | |||
| reranking_model_name: restConfigs.reranking_model.reranking_model_name, | |||
| }, | |||
| retrieval_model, | |||
| score_threshold_enabled, | |||
| datasets, | |||
| }) | |||
| }, [selectedDatasets, datasetConfigs]) | |||
| const { | |||
| defaultModel: rerankDefaultModel, | |||
| currentModel: isRerankDefaultModelVaild, | |||
| @@ -55,45 +99,68 @@ const ParamsConfig: FC = () => { | |||
| } as any | |||
| } | |||
| setDatasetConfigs(config) | |||
| setOpen(false) | |||
| setRerankSettingModalOpen(false) | |||
| } | |||
| const handleSetTempDataSetConfigs = (newDatasetConfigs: DatasetConfigs) => { | |||
| const { datasets, retrieval_model, score_threshold_enabled, ...restConfigs } = newDatasetConfigs | |||
| const retrievalConfig = getMultipleRetrievalConfig({ | |||
| top_k: restConfigs.top_k, | |||
| score_threshold: restConfigs.score_threshold, | |||
| reranking_model: restConfigs.reranking_model && { | |||
| provider: restConfigs.reranking_model.reranking_provider_name, | |||
| model: restConfigs.reranking_model.reranking_model_name, | |||
| }, | |||
| reranking_mode: restConfigs.reranking_mode, | |||
| weights: restConfigs.weights, | |||
| reranking_enable: restConfigs.reranking_enable, | |||
| }, selectedDatasets) | |||
| setTempDataSetConfigs({ | |||
| ...retrievalConfig, | |||
| reranking_model: restConfigs.reranking_model && { | |||
| reranking_provider_name: restConfigs.reranking_model.reranking_provider_name, | |||
| reranking_model_name: restConfigs.reranking_model.reranking_model_name, | |||
| }, | |||
| retrieval_model, | |||
| score_threshold_enabled, | |||
| datasets, | |||
| }) | |||
| } | |||
| return ( | |||
| <div> | |||
| <div | |||
| className={cn('flex items-center rounded-md h-7 px-3 space-x-1 text-gray-700 cursor-pointer hover:bg-gray-200', open && 'bg-gray-200')} | |||
| <Button | |||
| variant='ghost' | |||
| size='small' | |||
| className={cn('h-7', rerankSettingModalOpen && 'bg-components-button-ghost-bg-hover')} | |||
| onClick={() => { | |||
| setTempDataSetConfigs({ | |||
| ...datasetConfigs, | |||
| top_k: datasetConfigs.top_k || DATASET_DEFAULT.top_k, | |||
| score_threshold: datasetConfigs.score_threshold || DATASET_DEFAULT.score_threshold, | |||
| }) | |||
| setOpen(true) | |||
| setRerankSettingModalOpen(true) | |||
| }} | |||
| disabled={disabled} | |||
| > | |||
| <Settings04 className="w-[14px] h-[14px]" /> | |||
| <div className='text-xs font-medium'> | |||
| {t('appDebug.datasetConfig.params')} | |||
| </div> | |||
| </div> | |||
| <RiEqualizer2Line className='mr-1 w-3.5 h-3.5' /> | |||
| {t('dataset.retrievalSettings')} | |||
| </Button> | |||
| { | |||
| open && ( | |||
| rerankSettingModalOpen && ( | |||
| <Modal | |||
| isShow={open} | |||
| isShow={rerankSettingModalOpen} | |||
| onClose={() => { | |||
| setOpen(false) | |||
| setRerankSettingModalOpen(false) | |||
| }} | |||
| className='sm:min-w-[528px]' | |||
| title={t('appDebug.datasetConfig.settingTitle')} | |||
| > | |||
| <ConfigContent | |||
| datasetConfigs={tempDataSetConfigs} | |||
| onChange={setTempDataSetConfigs} | |||
| onChange={handleSetTempDataSetConfigs} | |||
| selectedDatasets={selectedDatasets} | |||
| /> | |||
| <div className='mt-6 flex justify-end'> | |||
| <Button className='mr-2 flex-shrink-0' onClick={() => { | |||
| setOpen(false) | |||
| setRerankSettingModalOpen(false) | |||
| }}>{t('common.operation.cancel')}</Button> | |||
| <Button variant='primary' className='flex-shrink-0' onClick={handleSave} >{t('common.operation.save')}</Button> | |||
| </div> | |||
| @@ -0,0 +1,112 @@ | |||
| import { memo, useCallback } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { | |||
| DEFAULT_WEIGHTED_SCORE, | |||
| WeightedScoreEnum, | |||
| } from '@/models/datasets' | |||
| import Slider from '@/app/components/base/slider' | |||
| import cn from '@/utils/classnames' | |||
| const formatNumber = (value: number) => { | |||
| if (value > 0 && value < 1) | |||
| return `0.${value * 10}` | |||
| else if (value === 1) | |||
| return '1.0' | |||
| return value | |||
| } | |||
| type Value = { | |||
| type: WeightedScoreEnum | |||
| value: number[] | |||
| } | |||
| type WeightedScoreProps = { | |||
| value: Value | |||
| onChange: (value: Value) => void | |||
| } | |||
| const WeightedScore = ({ | |||
| value, | |||
| onChange = () => {}, | |||
| }: WeightedScoreProps) => { | |||
| const { t } = useTranslation() | |||
| const options = [ | |||
| { | |||
| value: WeightedScoreEnum.SemanticFirst, | |||
| label: t('dataset.weightedScore.semanticFirst'), | |||
| }, | |||
| { | |||
| value: WeightedScoreEnum.KeywordFirst, | |||
| label: t('dataset.weightedScore.keywordFirst'), | |||
| }, | |||
| { | |||
| value: WeightedScoreEnum.Customized, | |||
| label: t('dataset.weightedScore.customized'), | |||
| }, | |||
| ] | |||
| const disabled = value.type !== WeightedScoreEnum.Customized | |||
| const handleTypeChange = useCallback((type: WeightedScoreEnum) => { | |||
| const result = { ...value, type } | |||
| if (type === WeightedScoreEnum.SemanticFirst) | |||
| result.value = [DEFAULT_WEIGHTED_SCORE.semanticFirst.semantic, DEFAULT_WEIGHTED_SCORE.semanticFirst.keyword] | |||
| if (type === WeightedScoreEnum.KeywordFirst) | |||
| result.value = [DEFAULT_WEIGHTED_SCORE.keywordFirst.semantic, DEFAULT_WEIGHTED_SCORE.keywordFirst.keyword] | |||
| onChange(result) | |||
| }, [value, onChange]) | |||
| return ( | |||
| <div> | |||
| <div className='flex items-center mb-1 space-x-4'> | |||
| { | |||
| options.map(option => ( | |||
| <div | |||
| key={option.value} | |||
| className='flex py-1.5 max-w-[calc((100%-32px)/3)] system-sm-regular text-text-secondary cursor-pointer' | |||
| onClick={() => handleTypeChange(option.value)} | |||
| > | |||
| <div | |||
| className={cn( | |||
| 'shrink-0 mr-2 w-4 h-4 bg-components-radio-bg border border-components-radio-border rounded-full shadow-xs', | |||
| value.type === option.value && 'border-[5px] border-components-radio-border-checked', | |||
| )} | |||
| ></div> | |||
| <div className='truncate' title={option.label}>{option.label}</div> | |||
| </div> | |||
| )) | |||
| } | |||
| </div> | |||
| <div className='flex items-center px-3 h-9 space-x-3 rounded-lg border border-components-panel-border'> | |||
| <div className='shrink-0 flex items-center w-[90px] system-xs-semibold-uppercase text-util-colors-blue-blue-500'> | |||
| <div className='mr-1 truncate uppercase' title={t('dataset.weightedScore.semantic') || ''}> | |||
| {t('dataset.weightedScore.semantic')} | |||
| </div> | |||
| {formatNumber(value.value[0])} | |||
| </div> | |||
| <Slider | |||
| className={cn('grow h-0.5 bg-gradient-to-r from-[#53B1FD] to-[#2ED3B7]', disabled && 'cursor-not-allowed')} | |||
| max={1.0} | |||
| min={0} | |||
| step={0.1} | |||
| value={value.value[0]} | |||
| onChange={v => onChange({ type: value.type, value: [v, (10 - v * 10) / 10] })} | |||
| disabled={disabled} | |||
| thumbClassName={cn(disabled && '!cursor-not-allowed')} | |||
| trackClassName='!bg-transparent' | |||
| /> | |||
| <div className='shrink-0 flex items-center justify-end w-[90px] system-xs-semibold-uppercase text-util-colors-cyan-cyan-500'> | |||
| {formatNumber(value.value[1])} | |||
| <div className='ml-1 truncate uppercase' title={t('dataset.weightedScore.keyword') || ''}> | |||
| {t('dataset.weightedScore.keyword')} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ) | |||
| } | |||
| export default memo(WeightedScore) | |||
| @@ -13,7 +13,8 @@ import type { DataSet } from '@/models/datasets' | |||
| import Button from '@/app/components/base/button' | |||
| import { fetchDatasets } from '@/service/datasets' | |||
| import Loading from '@/app/components/base/loading' | |||
| import { formatNumber } from '@/utils/format' | |||
| import Badge from '@/app/components/base/badge' | |||
| import { useKnowledge } from '@/hooks/use-knowledge' | |||
| export type ISelectDataSetProps = { | |||
| isShow: boolean | |||
| @@ -38,6 +39,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({ | |||
| const listRef = useRef<HTMLDivElement>(null) | |||
| const [page, setPage, getPage] = useGetState(1) | |||
| const [isNoMore, setIsNoMore] = useState(false) | |||
| const { formatIndexingTechniqueAndMethod } = useKnowledge() | |||
| useInfiniteScroll( | |||
| async () => { | |||
| @@ -45,7 +47,7 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({ | |||
| const { data, has_more } = await fetchDatasets({ url: '/datasets', params: { page } }) | |||
| setPage(getPage() + 1) | |||
| setIsNoMore(!has_more) | |||
| const newList = [...(datasets || []), ...data] | |||
| const newList = [...(datasets || []), ...data.filter(item => item.indexing_technique)] | |||
| setDataSets(newList) | |||
| setLoaded(true) | |||
| if (!selected.find(item => !item.name)) | |||
| @@ -136,14 +138,13 @@ const SelectDataSet: FC<ISelectDataSetProps> = ({ | |||
| <span className='ml-1 shrink-0 px-1 border boder-gray-200 rounded-md text-gray-500 text-xs font-normal leading-[18px]'>{t('dataset.unavailable')}</span> | |||
| )} | |||
| </div> | |||
| <div className={cn('shrink-0 flex text-xs text-gray-500 overflow-hidden whitespace-nowrap', !item.embedding_available && 'opacity-50')}> | |||
| <span className='max-w-[100px] overflow-hidden text-ellipsis whitespace-nowrap'>{formatNumber(item.word_count)}</span> | |||
| {t('appDebug.feature.dataSet.words')} | |||
| <span className='px-0.5'>·</span> | |||
| <span className='max-w-[100px] min-w-[8px] overflow-hidden text-ellipsis whitespace-nowrap'>{formatNumber(item.document_count)} </span> | |||
| {t('appDebug.feature.dataSet.textBlocks')} | |||
| </div> | |||
| { | |||
| item.indexing_technique && ( | |||
| <Badge | |||
| text={formatIndexingTechniqueAndMethod(item.indexing_technique, item.retrieval_model_dict?.search_method)} | |||
| /> | |||
| ) | |||
| } | |||
| </div> | |||
| ))} | |||
| </div> | |||
| @@ -259,7 +259,7 @@ const SettingsModal: FC<SettingsModalProps> = ({ | |||
| {/* Retrieval Method Config */} | |||
| <div className={rowClass}> | |||
| <div className={labelClass}> | |||
| <div className={cn(labelClass, 'w-auto min-w-[168px]')}> | |||
| <div> | |||
| <div>{t('datasetSettings.form.retrievalSetting.title')}</div> | |||
| <div className='leading-[18px] text-xs font-normal text-gray-500'> | |||
| @@ -268,7 +268,7 @@ const SettingsModal: FC<SettingsModalProps> = ({ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div className='w-[480px]'> | |||
| <div> | |||
| {indexMethod === 'high_quality' | |||
| ? ( | |||
| <RetrievalMethodConfig | |||
| @@ -46,7 +46,7 @@ import { fetchDatasets } from '@/service/datasets' | |||
| import { useProviderContext } from '@/context/provider-context' | |||
| import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app' | |||
| import { PromptMode } from '@/models/debug' | |||
| import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' | |||
| import { ANNOTATION_DEFAULT, DATASET_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config' | |||
| import SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset' | |||
| import { useModalContext } from '@/context/modal-context' | |||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | |||
| @@ -57,6 +57,10 @@ import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/comp | |||
| import { fetchCollectionList } from '@/service/tools' | |||
| import { type Collection } from '@/app/components/tools/types' | |||
| import { useStore as useAppStore } from '@/app/components/app/store' | |||
| import { | |||
| getMultipleRetrievalConfig, | |||
| getSelectedDatasetsMode, | |||
| } from '@/app/components/workflow/nodes/knowledge-retrieval/utils' | |||
| type PublishConfig = { | |||
| modelConfig: ModelConfig | |||
| @@ -174,14 +178,14 @@ const Configuration: FC = () => { | |||
| }, []) | |||
| const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>({ | |||
| retrieval_model: RETRIEVE_TYPE.oneWay, | |||
| retrieval_model: RETRIEVE_TYPE.multiWay, | |||
| reranking_model: { | |||
| reranking_provider_name: '', | |||
| reranking_model_name: '', | |||
| }, | |||
| top_k: 2, | |||
| top_k: DATASET_DEFAULT.top_k, | |||
| score_threshold_enabled: false, | |||
| score_threshold: 0.7, | |||
| score_threshold: DATASET_DEFAULT.score_threshold, | |||
| datasets: { | |||
| datasets: [], | |||
| }, | |||
| @@ -202,6 +206,7 @@ const Configuration: FC = () => { | |||
| const hasSetContextVar = !!contextVar | |||
| const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false) | |||
| const selectedIds = dataSets.map(item => item.id) | |||
| const [rerankSettingModalOpen, setRerankSettingModalOpen] = useState(false) | |||
| const handleSelect = (data: DataSet[]) => { | |||
| if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) { | |||
| hideSelectDataSet() | |||
| @@ -209,6 +214,7 @@ const Configuration: FC = () => { | |||
| } | |||
| formattingChangedDispatcher() | |||
| let newDatasets = data | |||
| if (data.find(item => !item.name)) { // has not loaded selected dataset | |||
| const newSelected = produce(data, (draft: any) => { | |||
| data.forEach((item, index) => { | |||
| @@ -220,11 +226,45 @@ const Configuration: FC = () => { | |||
| }) | |||
| }) | |||
| setDataSets(newSelected) | |||
| newDatasets = newSelected | |||
| } | |||
| else { | |||
| setDataSets(data) | |||
| } | |||
| hideSelectDataSet() | |||
| const { | |||
| allEconomic, | |||
| mixtureHighQualityAndEconomic, | |||
| inconsistentEmbeddingModel, | |||
| } = getSelectedDatasetsMode(newDatasets) | |||
| if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel) | |||
| setRerankSettingModalOpen(true) | |||
| const { datasets, retrieval_model, score_threshold_enabled, ...restConfigs } = datasetConfigs | |||
| const retrievalConfig = getMultipleRetrievalConfig({ | |||
| top_k: restConfigs.top_k, | |||
| score_threshold: restConfigs.score_threshold, | |||
| reranking_model: restConfigs.reranking_model && { | |||
| provider: restConfigs.reranking_model.reranking_provider_name, | |||
| model: restConfigs.reranking_model.reranking_model_name, | |||
| }, | |||
| reranking_mode: restConfigs.reranking_mode, | |||
| weights: restConfigs.weights, | |||
| reranking_enable: restConfigs.reranking_enable, | |||
| }, newDatasets) | |||
| setDatasetConfigs({ | |||
| ...retrievalConfig, | |||
| reranking_model: restConfigs.reranking_model && { | |||
| reranking_provider_name: restConfigs.reranking_model.reranking_provider_name, | |||
| reranking_model_name: restConfigs.reranking_model.reranking_model_name, | |||
| }, | |||
| retrieval_model, | |||
| score_threshold_enabled, | |||
| datasets, | |||
| }) | |||
| } | |||
| const [isShowHistoryModal, { setTrue: showHistoryModal, setFalse: hideHistoryModal }] = useBoolean(false) | |||
| @@ -509,7 +549,7 @@ const Configuration: FC = () => { | |||
| syncToPublishedConfig(config) | |||
| setPublishedConfig(config) | |||
| setDatasetConfigs({ | |||
| retrieval_model: RETRIEVE_TYPE.oneWay, | |||
| retrieval_model: RETRIEVE_TYPE.multiWay, | |||
| ...modelConfig.dataset_configs, | |||
| }) | |||
| setHasFetchedDetail(true) | |||
| @@ -744,6 +784,8 @@ const Configuration: FC = () => { | |||
| isShowVisionConfig, | |||
| visionConfig, | |||
| setVisionConfig: handleSetVisionConfig, | |||
| rerankSettingModalOpen, | |||
| setRerankSettingModalOpen, | |||
| }} | |||
| > | |||
| <> | |||
| @@ -0,0 +1,25 @@ | |||
| import { memo } from 'react' | |||
| import cn from '@/utils/classnames' | |||
| type BadgeProps = { | |||
| className?: string | |||
| text: string | |||
| } | |||
| const Badge = ({ | |||
| className, | |||
| text, | |||
| }: BadgeProps) => { | |||
| return ( | |||
| <div | |||
| className={cn( | |||
| 'inline-flex items-center px-[5px] h-5 rounded-[5px] border border-divider-deep system-2xs-medium-uppercase leading-3 text-text-tertiary', | |||
| className, | |||
| )} | |||
| > | |||
| {text} | |||
| </div> | |||
| ) | |||
| } | |||
| export default memo(Badge) | |||
| @@ -2,46 +2,185 @@ | |||
| @layer components { | |||
| .btn { | |||
| @apply inline-flex justify-center items-center border-[0.5px] font-medium cursor-pointer whitespace-nowrap shadow; | |||
| @apply inline-flex justify-center items-center cursor-pointer whitespace-nowrap; | |||
| } | |||
| .btn-disabled { | |||
| @apply opacity-60 cursor-not-allowed; | |||
| @apply cursor-not-allowed; | |||
| } | |||
| .btn-small { | |||
| @apply px-2 h-6 rounded-md text-xs | |||
| @apply px-2 h-6 rounded-md text-xs font-medium; | |||
| } | |||
| .btn-medium { | |||
| @apply px-3.5 h-8 rounded-lg text-[13px] | |||
| @apply px-3.5 h-8 rounded-lg text-[13px] leading-4 font-medium; | |||
| } | |||
| .btn-large { | |||
| @apply px-4 h-9 rounded-[10px] text-sm font-semibold | |||
| @apply px-4 h-9 rounded-[10px] text-sm font-semibold; | |||
| } | |||
| .btn-primary { | |||
| @apply | |||
| shadow | |||
| bg-components-button-primary-bg | |||
| border-components-button-primary-border | |||
| hover:bg-components-button-primary-bg-hover | |||
| hover:border-components-button-primary-border-hover | |||
| text-components-button-primary-text; | |||
| } | |||
| .btn-primary.btn-destructive { | |||
| @apply | |||
| bg-components-button-destructive-primary-bg | |||
| border-components-button-destructive-primary-border | |||
| hover:bg-components-button-destructive-primary-bg-hover | |||
| hover:border-components-button-destructive-primary-border-hover | |||
| text-components-button-destructive-primary-text; | |||
| } | |||
| .btn-primary.btn-disabled { | |||
| @apply | |||
| shadow-none | |||
| bg-components-button-primary-bg-disabled | |||
| border-components-button-primary-border-disabled | |||
| text-components-button-primary-text-disabled; | |||
| } | |||
| .btn-primary.btn-destructive.btn-disabled { | |||
| @apply | |||
| shadow-none | |||
| bg-components-button-destructive-primary-bg-disabled | |||
| border-components-button-destructive-primary-border-disabled | |||
| text-components-button-destructive-primary-text-disabled; | |||
| } | |||
| .btn-secondary { | |||
| @apply bg-white hover:bg-white/80 border-gray-200 hover:border-gray-300 text-gray-700; | |||
| @apply | |||
| border-[0.5px] | |||
| shadow-xs | |||
| bg-components-button-secondary-bg | |||
| border-components-button-secondary-border | |||
| hover:bg-components-button-secondary-bg-hover | |||
| hover:border-components-button-secondary-border-hover | |||
| text-components-button-secondary-text; | |||
| } | |||
| .btn-secondary.btn-disabled { | |||
| @apply | |||
| bg-components-button-secondary-bg-disabled | |||
| border-components-button-secondary-border-disabled | |||
| text-components-button-secondary-text-disabled; | |||
| } | |||
| .btn-secondary.btn-destructive { | |||
| @apply | |||
| bg-components-button-destructive-secondary-bg | |||
| border-components-button-destructive-secondary-border | |||
| hover:bg-components-button-destructive-secondary-bg-hover | |||
| hover:border-components-button-destructive-secondary-border-hover | |||
| text-components-button-destructive-secondary-text; | |||
| } | |||
| .btn-secondary.btn-destructive.btn-disabled { | |||
| @apply | |||
| bg-components-button-destructive-secondary-bg-disabled | |||
| border-components-button-destructive-secondary-border-disabled | |||
| text-components-button-destructive-secondary-text-disabled; | |||
| } | |||
| .btn-secondary-accent { | |||
| @apply bg-white hover:bg-white/80 border-gray-200 hover:border-gray-300 text-primary-600; | |||
| @apply | |||
| border-[0.5px] | |||
| shadow-xs | |||
| bg-components-button-secondary-bg | |||
| border-components-button-secondary-border | |||
| hover:bg-components-button-secondary-bg-hover | |||
| hover:border-components-button-secondary-border-hover | |||
| text-components-button-secondary-accent-text; | |||
| } | |||
| .btn-primary { | |||
| @apply bg-primary-600 hover:bg-primary-700 text-white; | |||
| .btn-secondary-accent.btn-disabled { | |||
| @apply | |||
| bg-components-button-secondary-bg-disabled | |||
| border-components-button-secondary-border-disabled | |||
| text-components-button-secondary-accent-text-disabled; | |||
| } | |||
| .btn-warning { | |||
| @apply bg-red-600 hover:bg-red-700 text-white; | |||
| @apply | |||
| bg-components-button-destructive-primary-bg | |||
| border-components-button-destructive-primary-border | |||
| hover:bg-components-button-destructive-primary-bg-hover | |||
| hover:border-components-button-destructive-primary-border-hover | |||
| text-components-button-destructive-primary-text; | |||
| } | |||
| .btn-ghost { | |||
| @apply bg-transparent hover:bg-gray-200 border-transparent shadow-none text-gray-700; | |||
| .btn-warning.btn-disabled { | |||
| @apply | |||
| bg-components-button-destructive-primary-bg-disabled | |||
| border-components-button-destructive-primary-border-disabled | |||
| text-components-button-destructive-primary-text-disabled; | |||
| } | |||
| .btn-tertiary { | |||
| @apply bg-[#F2F4F7] hover:bg-[#E9EBF0] border-transparent shadow-none text-gray-700; | |||
| @apply | |||
| bg-components-button-tertiary-bg | |||
| hover:bg-components-button-tertiary-bg-hover | |||
| text-components-button-tertiary-text; | |||
| } | |||
| .btn-tertiary.btn-disabled { | |||
| @apply | |||
| bg-components-button-tertiary-bg-disabled | |||
| text-components-button-tertiary-text-disabled; | |||
| } | |||
| .btn-tertiary.btn-destructive { | |||
| @apply | |||
| bg-components-button-destructive-tertiary-bg | |||
| hover:bg-components-button-destructive-tertiary-bg-hover | |||
| text-components-button-destructive-tertiary-text; | |||
| } | |||
| .btn-tertiary.btn-destructive.btn-disabled { | |||
| @apply | |||
| bg-components-button-destructive-tertiary-bg-disabled | |||
| text-components-button-destructive-tertiary-text-disabled; | |||
| } | |||
| .btn-ghost { | |||
| @apply | |||
| hover:bg-components-button-ghost-bg-hover | |||
| text-components-button-ghost-text; | |||
| } | |||
| .btn-ghost.btn-disabled { | |||
| @apply | |||
| text-components-button-ghost-text-disabled; | |||
| } | |||
| .btn-ghost.btn-destructive { | |||
| @apply | |||
| hover:bg-components-button-destructive-ghost-bg-hover | |||
| text-components-button-destructive-ghost-text; | |||
| } | |||
| .btn-ghost.btn-destructive.btn-disabled { | |||
| @apply | |||
| text-components-button-destructive-ghost-text-disabled; | |||
| } | |||
| .btn-ghost-accent { | |||
| @apply | |||
| hover:bg-state-accent-hover | |||
| text-components-button-secondary-accent-text; | |||
| } | |||
| .btn-ghost-accent.btn-disabled { | |||
| @apply | |||
| text-components-button-secondary-accent-text-disabled; | |||
| } | |||
| } | |||
| @@ -14,6 +14,7 @@ const buttonVariants = cva( | |||
| 'secondary': 'btn-secondary', | |||
| 'secondary-accent': 'btn-secondary-accent', | |||
| 'ghost': 'btn-ghost', | |||
| 'ghost-accent': 'btn-ghost-accent', | |||
| 'tertiary': 'btn-tertiary', | |||
| }, | |||
| size: { | |||
| @@ -30,16 +31,20 @@ const buttonVariants = cva( | |||
| ) | |||
| export type ButtonProps = { | |||
| destructive?: boolean | |||
| loading?: boolean | |||
| styleCss?: CSSProperties | |||
| } & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> | |||
| const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( | |||
| ({ className, variant, size, loading, styleCss, children, ...props }, ref) => { | |||
| ({ className, variant, size, destructive, loading, styleCss, children, ...props }, ref) => { | |||
| return ( | |||
| <button | |||
| type='button' | |||
| className={classNames(buttonVariants({ variant, size, className }))} | |||
| className={classNames( | |||
| buttonVariants({ variant, size, className }), | |||
| destructive && 'btn-destructive', | |||
| )} | |||
| ref={ref} | |||
| style={styleCss} | |||
| {...props} | |||
| @@ -1,7 +1,6 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import React from 'react' | |||
| import s from './style.module.css' | |||
| import cn from '@/utils/classnames' | |||
| type Props = { | |||
| @@ -29,7 +28,12 @@ const RadioCard: FC<Props> = ({ | |||
| chosenConfigWrapClassName, | |||
| }) => { | |||
| return ( | |||
| <div className={cn(s.item, isChosen && s.active)}> | |||
| <div | |||
| className={cn( | |||
| 'border border-components-option-card-option-border bg-components-option-card-option-bg rounded-xl hover:shadow-xs cursor-pointer', | |||
| isChosen && 'bg-components-option-card-option-selected-bg border-components-panel-border shadow-xs', | |||
| )} | |||
| > | |||
| <div className='flex py-3 pl-3 pr-4' onClick={onChosen}> | |||
| <div className={cn(iconBgClassName, 'mr-3 shrink-0 flex w-8 justify-center h-8 items-center rounded-lg')}> | |||
| {icon} | |||
| @@ -40,12 +44,15 @@ const RadioCard: FC<Props> = ({ | |||
| </div> | |||
| {!noRadio && ( | |||
| <div className='shrink-0 flex items-center h-8'> | |||
| <div className={s.radio}></div> | |||
| <div className={cn( | |||
| 'w-4 h-4 border border-components-radio-border bg-components-radio-bg shadow-xs rounded-full', | |||
| isChosen && 'border-[5px] border-components-radio-border-checked', | |||
| )}></div> | |||
| </div> | |||
| )} | |||
| </div> | |||
| {((isChosen && chosenConfig) || noRadio) && ( | |||
| <div className={cn(chosenConfigWrapClassName, 'pt-2 px-14 pb-6 border-t border-gray-200')}> | |||
| <div className={cn(chosenConfigWrapClassName, 'p-3 border-t border-gray-200')}> | |||
| {chosenConfig} | |||
| </div> | |||
| )} | |||
| @@ -6,7 +6,7 @@ import cn from '@/utils/classnames' | |||
| type Props = { | |||
| className?: string | |||
| title: string | |||
| title: string | JSX.Element | null | |||
| description: string | |||
| isChosen: boolean | |||
| onChosen: () => void | |||
| @@ -1,25 +0,0 @@ | |||
| .item { | |||
| @apply relative rounded-xl border border-gray-100 cursor-pointer; | |||
| background-color: #fcfcfd; | |||
| } | |||
| .item.active { | |||
| border-width: 1.5px; | |||
| border-color: #528BFF; | |||
| box-shadow: 0px 1px 3px rgba(16, 24, 40, 0.1), 0px 1px 2px rgba(16, 24, 40, 0.06); | |||
| } | |||
| .item:hover { | |||
| background-color: #ffffff; | |||
| border-color: #B2CCFF; | |||
| box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); | |||
| } | |||
| .radio { | |||
| @apply w-4 h-4 border-[2px] border-gray-200 rounded-full; | |||
| } | |||
| .item.active .radio { | |||
| border-width: 5px; | |||
| border-color: #155EEF; | |||
| } | |||
| @@ -4,6 +4,8 @@ import './style.css' | |||
| type ISliderProps = { | |||
| className?: string | |||
| thumbClassName?: string | |||
| trackClassName?: string | |||
| value: number | |||
| max?: number | |||
| min?: number | |||
| @@ -12,16 +14,26 @@ type ISliderProps = { | |||
| onChange: (value: number) => void | |||
| } | |||
| const Slider: React.FC<ISliderProps> = ({ className, max, min, step, value, disabled, onChange }) => { | |||
| const Slider: React.FC<ISliderProps> = ({ | |||
| className, | |||
| thumbClassName, | |||
| trackClassName, | |||
| max, | |||
| min, | |||
| step, | |||
| value, | |||
| disabled, | |||
| onChange, | |||
| }) => { | |||
| return <ReactSlider | |||
| disabled={disabled} | |||
| value={isNaN(value) ? 0 : value} | |||
| min={min || 0} | |||
| max={max || 100} | |||
| step={step || 1} | |||
| className={cn(className, 'slider')} | |||
| thumbClassName="slider-thumb" | |||
| trackClassName="slider-track" | |||
| className={cn('slider', className)} | |||
| thumbClassName={cn('slider-thumb', thumbClassName)} | |||
| trackClassName={cn('slider-track', trackClassName)} | |||
| onChange={onChange} | |||
| /> | |||
| } | |||
| @@ -3,6 +3,7 @@ import type { | |||
| DefaultModelResponse, | |||
| Model, | |||
| } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import { RerankingModeEnum } from '@/models/datasets' | |||
| export const isReRankModelSelected = ({ | |||
| rerankDefaultModel, | |||
| @@ -32,7 +33,7 @@ export const isReRankModelSelected = ({ | |||
| if ( | |||
| indexMethod === 'high_quality' | |||
| && (retrievalConfig.reranking_enable || retrievalConfig.search_method === RETRIEVE_METHOD.hybrid) | |||
| && (retrievalConfig.search_method === RETRIEVE_METHOD.hybrid && retrievalConfig.reranking_mode !== RerankingModeEnum.WeightedScore) | |||
| && !rerankModelSelected | |||
| ) | |||
| return false | |||
| @@ -15,6 +15,11 @@ import type { RetrievalConfig } from '@/types/app' | |||
| import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' | |||
| import { useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | |||
| import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import { | |||
| RerankingModeEnum, | |||
| WeightedScoreEnum, | |||
| } from '@/models/datasets' | |||
| import WeightedScore from '@/app/components/app/configuration/dataset-config/params-config/weighted-score' | |||
| type Props = { | |||
| type: RETRIEVE_METHOD | |||
| @@ -34,6 +39,7 @@ const RetrievalParamConfig: FC<Props> = ({ | |||
| defaultModel: rerankDefaultModel, | |||
| modelList: rerankModelList, | |||
| } = useModelListAndDefaultModel(ModelTypeEnum.rerank) | |||
| const isHybridSearch = type === RETRIEVE_METHOD.hybrid | |||
| const rerankModel = (() => { | |||
| if (value.reranking_model) { | |||
| @@ -50,9 +56,47 @@ const RetrievalParamConfig: FC<Props> = ({ | |||
| } | |||
| })() | |||
| const handleChangeRerankMode = (v: RerankingModeEnum) => { | |||
| if (v === value.reranking_mode) | |||
| return | |||
| const result = { | |||
| ...value, | |||
| reranking_mode: v, | |||
| } | |||
| if (!result.weights && v === RerankingModeEnum.WeightedScore) { | |||
| result.weights = { | |||
| weight_type: WeightedScoreEnum.Customized, | |||
| vector_setting: { | |||
| vector_weight: 0.5, | |||
| embedding_provider_name: '', | |||
| embedding_model_name: '', | |||
| }, | |||
| keyword_setting: { | |||
| keyword_weight: 0.5, | |||
| }, | |||
| } | |||
| } | |||
| onChange(result) | |||
| } | |||
| const rerankingModeOptions = [ | |||
| { | |||
| value: RerankingModeEnum.WeightedScore, | |||
| label: t('dataset.weightedScore.title'), | |||
| tips: t('dataset.weightedScore.description'), | |||
| }, | |||
| { | |||
| value: RerankingModeEnum.RerankingModel, | |||
| label: t('common.modelProvider.rerankModel.key'), | |||
| tips: t('common.modelProvider.rerankModel.tip'), | |||
| }, | |||
| ] | |||
| return ( | |||
| <div> | |||
| {!isEconomical && ( | |||
| {!isEconomical && !isHybridSearch && ( | |||
| <div> | |||
| <div className='flex h-8 items-center text-[13px] font-medium text-gray-900 space-x-2'> | |||
| {canToggleRerankModalEnable && ( | |||
| @@ -75,10 +119,10 @@ const RetrievalParamConfig: FC<Props> = ({ | |||
| </div> | |||
| </div> | |||
| <ModelSelector | |||
| triggerClassName={`${!value.reranking_enable && type !== RETRIEVE_METHOD.hybrid && '!opacity-60 !cursor-not-allowed'}`} | |||
| triggerClassName={`${!value.reranking_enable && '!opacity-60 !cursor-not-allowed'}`} | |||
| defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }} | |||
| modelList={rerankModelList} | |||
| readonly={!value.reranking_enable && type !== RETRIEVE_METHOD.hybrid} | |||
| readonly={!value.reranking_enable} | |||
| onSelect={(v) => { | |||
| onChange({ | |||
| ...value, | |||
| @@ -91,40 +135,152 @@ const RetrievalParamConfig: FC<Props> = ({ | |||
| /> | |||
| </div> | |||
| )} | |||
| <div className={cn(!isEconomical && 'mt-4', 'flex space-between space-x-6')}> | |||
| <TopKItem | |||
| className='grow' | |||
| value={value.top_k} | |||
| onChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| top_k: v, | |||
| }) | |||
| }} | |||
| enable={true} | |||
| /> | |||
| {(!isEconomical && !(value.search_method === RETRIEVE_METHOD.fullText && !value.reranking_enable)) && ( | |||
| <ScoreThresholdItem | |||
| className='grow' | |||
| value={value.score_threshold} | |||
| onChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| score_threshold: v, | |||
| }) | |||
| }} | |||
| enable={value.score_threshold_enabled} | |||
| hasSwitch={true} | |||
| onSwitchChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| score_threshold_enabled: v, | |||
| }) | |||
| }} | |||
| /> | |||
| )} | |||
| </div> | |||
| { | |||
| !isHybridSearch && ( | |||
| <div className={cn(!isEconomical && 'mt-4', 'flex space-between space-x-6')}> | |||
| <TopKItem | |||
| className='grow' | |||
| value={value.top_k} | |||
| onChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| top_k: v, | |||
| }) | |||
| }} | |||
| enable={true} | |||
| /> | |||
| {(!isEconomical && !(value.search_method === RETRIEVE_METHOD.fullText && !value.reranking_enable)) && ( | |||
| <ScoreThresholdItem | |||
| className='grow' | |||
| value={value.score_threshold} | |||
| onChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| score_threshold: v, | |||
| }) | |||
| }} | |||
| enable={value.score_threshold_enabled} | |||
| hasSwitch={true} | |||
| onSwitchChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| score_threshold_enabled: v, | |||
| }) | |||
| }} | |||
| /> | |||
| )} | |||
| </div> | |||
| ) | |||
| } | |||
| { | |||
| isHybridSearch && ( | |||
| <> | |||
| <div className='flex items-center justify-between'> | |||
| { | |||
| rerankingModeOptions.map(option => ( | |||
| <div | |||
| key={option.value} | |||
| className={cn( | |||
| 'flex items-center justify-center mb-4 w-[calc((100%-8px)/2)] h-8 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer system-sm-medium text-text-secondary', | |||
| value.reranking_mode === RerankingModeEnum.WeightedScore && option.value === RerankingModeEnum.WeightedScore && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary', | |||
| value.reranking_mode !== RerankingModeEnum.WeightedScore && option.value !== RerankingModeEnum.WeightedScore && 'border-[1.5px] border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary', | |||
| )} | |||
| onClick={() => handleChangeRerankMode(option.value)} | |||
| > | |||
| <div className='truncate'>{option.label}</div> | |||
| <Tooltip | |||
| popupContent={<div className='w-[200px]'>{option.tips}</div>} | |||
| hideArrow | |||
| > | |||
| <RiQuestionLine className='ml-0.5 w-3.5 h-4.5 text-text-quaternary' /> | |||
| </Tooltip> | |||
| </div> | |||
| )) | |||
| } | |||
| </div> | |||
| { | |||
| value.reranking_mode === RerankingModeEnum.WeightedScore && ( | |||
| <WeightedScore | |||
| value={{ | |||
| type: value.weights!.weight_type, | |||
| value: [ | |||
| value.weights!.vector_setting.vector_weight, | |||
| value.weights!.keyword_setting.keyword_weight, | |||
| ], | |||
| }} | |||
| onChange={(v) => { | |||
| onChange({ | |||
| ...value, | |||
| weights: { | |||
| ...value.weights!, | |||
| weight_type: v.type, | |||
| vector_setting: { | |||
| ...value.weights!.vector_setting, | |||
| vector_weight: v.value[0], | |||
| }, | |||
| keyword_setting: { | |||
| ...value.weights!.keyword_setting, | |||
| keyword_weight: v.value[1], | |||
| }, | |||
| }, | |||
| }) | |||
| }} | |||
| /> | |||
| ) | |||
| } | |||
| { | |||
| value.reranking_mode !== RerankingModeEnum.WeightedScore && ( | |||
| <ModelSelector | |||
| triggerClassName={`${!value.reranking_enable && '!opacity-60 !cursor-not-allowed'}`} | |||
| defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }} | |||
| modelList={rerankModelList} | |||
| readonly={!value.reranking_enable} | |||
| onSelect={(v) => { | |||
| onChange({ | |||
| ...value, | |||
| reranking_model: { | |||
| reranking_provider_name: v.provider, | |||
| reranking_model_name: v.model, | |||
| }, | |||
| }) | |||
| }} | |||
| /> | |||
| ) | |||
| } | |||
| <div className={cn(!isEconomical && 'mt-4', 'flex space-between space-x-6')}> | |||
| <TopKItem | |||
| className='grow' | |||
| value={value.top_k} | |||
| onChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| top_k: v, | |||
| }) | |||
| }} | |||
| enable={true} | |||
| /> | |||
| <ScoreThresholdItem | |||
| className='grow' | |||
| value={value.score_threshold} | |||
| onChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| score_threshold: v, | |||
| }) | |||
| }} | |||
| enable={value.score_threshold_enabled} | |||
| hasSwitch={true} | |||
| onSwitchChange={(_key, v) => { | |||
| onChange({ | |||
| ...value, | |||
| score_threshold_enabled: v, | |||
| }) | |||
| }} | |||
| /> | |||
| </div> | |||
| </> | |||
| ) | |||
| } | |||
| </div> | |||
| ) | |||
| } | |||
| @@ -113,6 +113,10 @@ const Form = () => { | |||
| retrievalConfig, | |||
| indexMethod, | |||
| }) | |||
| if (postRetrievalConfig.weights) { | |||
| postRetrievalConfig.weights.vector_setting.embedding_provider_name = currentDataset?.embedding_model_provider || '' | |||
| postRetrievalConfig.weights.vector_setting.embedding_model_name = currentDataset?.embedding_model || '' | |||
| } | |||
| try { | |||
| setLoading(true) | |||
| const requestParams = { | |||
| @@ -22,7 +22,6 @@ const Icon = ({ svgString, active }: { svgString: string; active: boolean }) => | |||
| return null | |||
| const parser = new DOMParser() | |||
| const doc = parser.parseFromString(svg, 'image/svg+xml') | |||
| console.log(doc.documentElement) | |||
| return doc.documentElement | |||
| } | |||
| useMount(() => { | |||
| @@ -4,15 +4,17 @@ import React, { useCallback } from 'react' | |||
| import { useBoolean } from 'ahooks' | |||
| import { | |||
| RiDeleteBinLine, | |||
| RiEditLine, | |||
| } from '@remixicon/react' | |||
| import type { DataSet } from '@/models/datasets' | |||
| import { DataSourceType } from '@/models/datasets' | |||
| import { Settings01 } from '@/app/components/base/icons/src/vender/line/general' | |||
| import FileIcon from '@/app/components/base/file-icon' | |||
| import { Folder } from '@/app/components/base/icons/src/vender/solid/files' | |||
| import SettingsModal from '@/app/components/app/configuration/dataset-config/settings-modal' | |||
| import Drawer from '@/app/components/base/drawer' | |||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | |||
| import Badge from '@/app/components/base/badge' | |||
| import { useKnowledge } from '@/hooks/use-knowledge' | |||
| type Props = { | |||
| payload: DataSet | |||
| @@ -29,6 +31,7 @@ const DatasetItem: FC<Props> = ({ | |||
| }) => { | |||
| const media = useBreakpoints() | |||
| const isMobile = media === MediaType.mobile | |||
| const { formatIndexingTechniqueAndMethod } = useKnowledge() | |||
| const [isShowSettingsModal, { | |||
| setTrue: showSettingsModal, | |||
| @@ -62,7 +65,7 @@ const DatasetItem: FC<Props> = ({ | |||
| className='flex items-center justify-center w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | |||
| onClick={showSettingsModal} | |||
| > | |||
| <Settings01 className='w-4 h-4 text-gray-500' /> | |||
| <RiEditLine className='w-4 h-4 text-gray-500' /> | |||
| </div> | |||
| <div | |||
| className='flex items-center justify-center w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | |||
| @@ -72,6 +75,10 @@ const DatasetItem: FC<Props> = ({ | |||
| </div> | |||
| </div> | |||
| )} | |||
| <Badge | |||
| className='group-hover/dataset-item:hidden shrink-0' | |||
| text={formatIndexingTechniqueAndMethod(payload.indexing_technique, payload.retrieval_model_dict?.search_method)} | |||
| /> | |||
| {isShowSettingsModal && ( | |||
| <Drawer isOpen={isShowSettingsModal} onClose={hideSettingsModal} footer={null} mask={isMobile} panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'> | |||
| @@ -1,8 +1,8 @@ | |||
| 'use client' | |||
| import type { FC } from 'react' | |||
| import React, { useCallback, useState } from 'react' | |||
| import { RiEqualizer2Line } from '@remixicon/react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import { RiArrowDownSLine } from '@remixicon/react' | |||
| import type { MultipleRetrievalConfig, SingleRetrievalConfig } from '../types' | |||
| import type { ModelConfig } from '../../../types' | |||
| import cn from '@/utils/classnames' | |||
| @@ -16,10 +16,9 @@ import { RETRIEVE_TYPE } from '@/types/app' | |||
| import { DATASET_DEFAULT } from '@/config' | |||
| import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | |||
| import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | |||
| import type { | |||
| DatasetConfigs, | |||
| } from '@/models/debug' | |||
| import Button from '@/app/components/base/button' | |||
| import type { DatasetConfigs } from '@/models/debug' | |||
| import type { DataSet } from '@/models/datasets' | |||
| type Props = { | |||
| payload: { | |||
| @@ -33,6 +32,9 @@ type Props = { | |||
| onSingleRetrievalModelChange?: (config: ModelConfig) => void | |||
| onSingleRetrievalModelParamsChange?: (config: ModelConfig) => void | |||
| readonly?: boolean | |||
| openFromProps?: boolean | |||
| onOpenFromPropsChange?: (openFromProps: boolean) => void | |||
| selectedDatasets: DataSet[] | |||
| } | |||
| const RetrievalConfig: FC<Props> = ({ | |||
| @@ -43,10 +45,18 @@ const RetrievalConfig: FC<Props> = ({ | |||
| onSingleRetrievalModelChange, | |||
| onSingleRetrievalModelParamsChange, | |||
| readonly, | |||
| openFromProps, | |||
| onOpenFromPropsChange, | |||
| selectedDatasets, | |||
| }) => { | |||
| const { t } = useTranslation() | |||
| const [open, setOpen] = useState(false) | |||
| const mergedOpen = openFromProps !== undefined ? openFromProps : open | |||
| const handleOpen = useCallback((newOpen: boolean) => { | |||
| setOpen(newOpen) | |||
| onOpenFromPropsChange?.(newOpen) | |||
| }, [onOpenFromPropsChange]) | |||
| const { | |||
| defaultModel: rerankDefaultModel, | |||
| @@ -72,16 +82,18 @@ const RetrievalConfig: FC<Props> = ({ | |||
| provider: configs.reranking_model?.reranking_provider_name, | |||
| model: configs.reranking_model?.reranking_model_name, | |||
| }), | |||
| reranking_mode: configs.reranking_mode, | |||
| weights: configs.weights as any, | |||
| reranking_enable: configs.reranking_enable, | |||
| }) | |||
| }, [onMultipleRetrievalConfigChange, payload.retrieval_mode, rerankDefaultModel?.provider?.provider, rerankDefaultModel?.model, onRetrievalModeChange]) | |||
| return ( | |||
| <PortalToFollowElem | |||
| open={open} | |||
| onOpenChange={setOpen} | |||
| open={mergedOpen} | |||
| onOpenChange={handleOpen} | |||
| placement='bottom-end' | |||
| offset={{ | |||
| // mainAxis: 12, | |||
| crossAxis: -2, | |||
| }} | |||
| > | |||
| @@ -89,13 +101,18 @@ const RetrievalConfig: FC<Props> = ({ | |||
| onClick={() => { | |||
| if (readonly) | |||
| return | |||
| setOpen(v => !v) | |||
| handleOpen(!mergedOpen) | |||
| }} | |||
| > | |||
| <div className={cn(!readonly && 'cursor-pointer', open && 'bg-gray-100', 'flex items-center h-6 px-2 rounded-md hover:bg-gray-100 group select-none')}> | |||
| <div className={cn(open ? 'text-gray-700' : 'text-gray-500', 'leading-[18px] text-xs font-medium group-hover:bg-gray-100')}>{payload.retrieval_mode === RETRIEVE_TYPE.oneWay ? t('appDebug.datasetConfig.retrieveOneWay.title') : t('appDebug.datasetConfig.retrieveMultiWay.title')}</div> | |||
| {!readonly && <RiArrowDownSLine className='w-3 h-3 ml-1' />} | |||
| </div> | |||
| <Button | |||
| variant='ghost' | |||
| size='small' | |||
| disabled={readonly} | |||
| className={cn(open && 'bg-components-button-ghost-bg-hover')} | |||
| > | |||
| <RiEqualizer2Line className='mr-1 w-3.5 h-3.5' /> | |||
| {t('dataset.retrievalSettings')} | |||
| </Button> | |||
| </PortalToFollowElemTrigger> | |||
| <PortalToFollowElemContent style={{ zIndex: 1001 }}> | |||
| <div className='w-[404px] pt-3 pb-4 px-4 shadow-xl rounded-2xl border border-gray-200 bg-white'> | |||
| @@ -103,21 +120,24 @@ const RetrievalConfig: FC<Props> = ({ | |||
| datasetConfigs={ | |||
| { | |||
| retrieval_model: payload.retrieval_mode, | |||
| reranking_model: !multiple_retrieval_config?.reranking_model?.provider | |||
| reranking_model: multiple_retrieval_config?.reranking_model?.provider | |||
| ? { | |||
| reranking_provider_name: rerankDefaultModel?.provider?.provider || '', | |||
| reranking_model_name: rerankDefaultModel?.model || '', | |||
| reranking_provider_name: multiple_retrieval_config.reranking_model?.provider, | |||
| reranking_model_name: multiple_retrieval_config.reranking_model?.model, | |||
| } | |||
| : { | |||
| reranking_provider_name: multiple_retrieval_config?.reranking_model?.provider || '', | |||
| reranking_model_name: multiple_retrieval_config?.reranking_model?.model || '', | |||
| reranking_provider_name: '', | |||
| reranking_model_name: '', | |||
| }, | |||
| top_k: multiple_retrieval_config?.top_k || DATASET_DEFAULT.top_k, | |||
| score_threshold_enabled: !(multiple_retrieval_config?.score_threshold === undefined || multiple_retrieval_config?.score_threshold === null), | |||
| score_threshold_enabled: !(multiple_retrieval_config?.score_threshold === undefined || multiple_retrieval_config.score_threshold === null), | |||
| score_threshold: multiple_retrieval_config?.score_threshold, | |||
| datasets: { | |||
| datasets: [], | |||
| }, | |||
| reranking_mode: multiple_retrieval_config?.reranking_mode, | |||
| weights: multiple_retrieval_config?.weights, | |||
| reranking_enable: multiple_retrieval_config?.reranking_enable, | |||
| } | |||
| } | |||
| onChange={handleChange} | |||
| @@ -125,6 +145,7 @@ const RetrievalConfig: FC<Props> = ({ | |||
| singleRetrievalModelConfig={singleRetrievalModelConfig} | |||
| onSingleRetrievalModelChange={onSingleRetrievalModelChange} | |||
| onSingleRetrievalModelParamsChange={onSingleRetrievalModelParamsChange} | |||
| selectedDatasets={selectedDatasets} | |||
| /> | |||
| </div> | |||
| </PortalToFollowElemContent> | |||
| @@ -2,7 +2,7 @@ import { BlockEnum } from '../../types' | |||
| import type { NodeDefault } from '../../types' | |||
| import type { KnowledgeRetrievalNodeType } from './types' | |||
| import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants' | |||
| import { DATASET_DEFAULT } from '@/config' | |||
| import { RETRIEVE_TYPE } from '@/types/app' | |||
| const i18nPrefix = 'workflow' | |||
| @@ -10,7 +10,12 @@ const nodeDefault: NodeDefault<KnowledgeRetrievalNodeType> = { | |||
| defaultValue: { | |||
| query_variable_selector: [], | |||
| dataset_ids: [], | |||
| retrieval_mode: RETRIEVE_TYPE.oneWay, | |||
| retrieval_mode: RETRIEVE_TYPE.multiWay, | |||
| multiple_retrieval_config: { | |||
| top_k: DATASET_DEFAULT.top_k, | |||
| score_threshold: undefined, | |||
| reranking_enable: false, | |||
| }, | |||
| }, | |||
| getAvailablePrevNodes(isChatMode: boolean) { | |||
| const nodes = isChatMode | |||
| @@ -0,0 +1,14 @@ | |||
| import { useMemo } from 'react' | |||
| import { getSelectedDatasetsMode } from './utils' | |||
| import type { | |||
| DataSet, | |||
| SelectedDatasetsMode, | |||
| } from '@/models/datasets' | |||
| export const useSelectedDatasetsMode = (datasets: DataSet[]) => { | |||
| const selectedDatasetsMode: SelectedDatasetsMode = useMemo(() => { | |||
| return getSelectedDatasetsMode(datasets) | |||
| }, [datasets]) | |||
| return selectedDatasetsMode | |||
| } | |||
| @@ -1,5 +1,8 @@ | |||
| import type { FC } from 'react' | |||
| import React from 'react' | |||
| import { | |||
| memo, | |||
| useCallback, | |||
| } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| import VarReferencePicker from '../_base/components/variable/var-reference-picker' | |||
| import useConfig from './use-config' | |||
| @@ -41,8 +44,14 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ | |||
| query, | |||
| setQuery, | |||
| runResult, | |||
| rerankModelOpen, | |||
| setRerankModelOpen, | |||
| } = useConfig(id, data) | |||
| const handleOpenFromPropsChange = useCallback((openFromProps: boolean) => { | |||
| setRerankModelOpen(openFromProps) | |||
| }, [setRerankModelOpen]) | |||
| return ( | |||
| <div className='mt-2'> | |||
| <div className='px-4 pb-4 space-y-4'> | |||
| @@ -75,7 +84,10 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ | |||
| singleRetrievalModelConfig={inputs.single_retrieval_config?.model} | |||
| onSingleRetrievalModelChange={handleModelChanged as any} | |||
| onSingleRetrievalModelParamsChange={handleCompletionParamsChange} | |||
| readonly={readOnly} | |||
| readonly={readOnly || !selectedDatasets.length} | |||
| openFromProps={rerankModelOpen} | |||
| onOpenFromPropsChange={handleOpenFromPropsChange} | |||
| selectedDatasets={selectedDatasets} | |||
| /> | |||
| {!readOnly && (<div className='w-px h-3 bg-gray-200'></div>)} | |||
| {!readOnly && ( | |||
| @@ -162,4 +174,4 @@ const Panel: FC<NodePanelProps<KnowledgeRetrievalNodeType>> = ({ | |||
| ) | |||
| } | |||
| export default React.memo(Panel) | |||
| export default memo(Panel) | |||
| @@ -1,5 +1,9 @@ | |||
| import type { CommonNodeType, ModelConfig, ValueSelector } from '@/app/components/workflow/types' | |||
| import type { RETRIEVE_TYPE } from '@/types/app' | |||
| import type { | |||
| RerankingModeEnum, | |||
| WeightedScoreEnum, | |||
| } from '@/models/datasets' | |||
| export type MultipleRetrievalConfig = { | |||
| top_k: number | |||
| @@ -8,6 +12,19 @@ export type MultipleRetrievalConfig = { | |||
| provider: string | |||
| model: string | |||
| } | |||
| reranking_mode?: RerankingModeEnum | |||
| weights?: { | |||
| weight_type: WeightedScoreEnum | |||
| vector_setting: { | |||
| vector_weight: number | |||
| embedding_provider_name: string | |||
| embedding_model_name: string | |||
| } | |||
| keyword_setting: { | |||
| keyword_weight: number | |||
| } | |||
| } | |||
| reranking_enable?: boolean | |||
| } | |||
| export type SingleRetrievalConfig = { | |||
| @@ -1,4 +1,9 @@ | |||
| import { useCallback, useEffect, useRef, useState } from 'react' | |||
| import { | |||
| useCallback, | |||
| useEffect, | |||
| useRef, | |||
| useState, | |||
| } from 'react' | |||
| import produce from 'immer' | |||
| import { isEqual } from 'lodash-es' | |||
| import type { ValueSelector, Var } from '../../types' | |||
| @@ -8,6 +13,10 @@ import { | |||
| useWorkflow, | |||
| } from '../../hooks' | |||
| import type { KnowledgeRetrievalNodeType, MultipleRetrievalConfig } from './types' | |||
| import { | |||
| getMultipleRetrievalConfig, | |||
| getSelectedDatasetsMode, | |||
| } from './utils' | |||
| import { RETRIEVE_TYPE } from '@/types/app' | |||
| import { DATASET_DEFAULT } from '@/config' | |||
| import type { DataSet } from '@/models/datasets' | |||
| @@ -126,34 +135,20 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { | |||
| draft.multiple_retrieval_config = { | |||
| top_k: multipleRetrievalConfig?.top_k || DATASET_DEFAULT.top_k, | |||
| score_threshold: multipleRetrievalConfig?.score_threshold, | |||
| reranking_model: payload.retrieval_mode === RETRIEVE_TYPE.oneWay | |||
| ? undefined | |||
| : (!multipleRetrievalConfig?.reranking_model?.provider | |||
| ? { | |||
| provider: rerankDefaultModel?.provider?.provider || '', | |||
| model: rerankDefaultModel?.model || '', | |||
| } | |||
| : multipleRetrievalConfig?.reranking_model), | |||
| reranking_model: multipleRetrievalConfig?.reranking_model, | |||
| } | |||
| }) | |||
| setInputs(newInput) | |||
| // eslint-disable-next-line react-hooks/exhaustive-deps | |||
| }, [currentProvider?.provider, currentModel, rerankDefaultModel]) | |||
| const [selectedDatasets, setSelectedDatasets] = useState<DataSet[]>([]) | |||
| const [rerankModelOpen, setRerankModelOpen] = useState(false) | |||
| const handleRetrievalModeChange = useCallback((newMode: RETRIEVE_TYPE) => { | |||
| const newInputs = produce(inputs, (draft) => { | |||
| draft.retrieval_mode = newMode | |||
| if (newMode === RETRIEVE_TYPE.multiWay) { | |||
| draft.multiple_retrieval_config = { | |||
| top_k: draft.multiple_retrieval_config?.top_k || DATASET_DEFAULT.top_k, | |||
| score_threshold: draft.multiple_retrieval_config?.score_threshold, | |||
| reranking_model: !draft.multiple_retrieval_config?.reranking_model?.provider | |||
| ? { | |||
| provider: rerankDefaultModel?.provider?.provider || '', | |||
| model: rerankDefaultModel?.model || '', | |||
| } | |||
| : draft.multiple_retrieval_config?.reranking_model, | |||
| } | |||
| const multipleRetrievalConfig = draft.multiple_retrieval_config | |||
| draft.multiple_retrieval_config = getMultipleRetrievalConfig(multipleRetrievalConfig!, selectedDatasets) | |||
| } | |||
| else { | |||
| const hasSetModel = draft.single_retrieval_config?.model?.provider | |||
| @@ -170,17 +165,16 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { | |||
| } | |||
| }) | |||
| setInputs(newInputs) | |||
| }, [currentModel?.model, currentModel?.model_properties?.mode, currentProvider?.provider, inputs, rerankDefaultModel?.model, rerankDefaultModel?.provider?.provider, setInputs]) | |||
| }, [currentModel?.model, currentModel?.model_properties?.mode, currentProvider?.provider, inputs, setInputs, selectedDatasets]) | |||
| const handleMultipleRetrievalConfigChange = useCallback((newConfig: MultipleRetrievalConfig) => { | |||
| const newInputs = produce(inputs, (draft) => { | |||
| draft.multiple_retrieval_config = newConfig | |||
| draft.multiple_retrieval_config = getMultipleRetrievalConfig(newConfig!, selectedDatasets) | |||
| }) | |||
| setInputs(newInputs) | |||
| }, [inputs, setInputs]) | |||
| }, [inputs, setInputs, selectedDatasets]) | |||
| // datasets | |||
| const [selectedDatasets, setSelectedDatasets] = useState<DataSet[]>([]) | |||
| useEffect(() => { | |||
| (async () => { | |||
| const inputs = inputRef.current | |||
| @@ -210,12 +204,25 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { | |||
| }, []) | |||
| const handleOnDatasetsChange = useCallback((newDatasets: DataSet[]) => { | |||
| const { | |||
| allEconomic, | |||
| mixtureHighQualityAndEconomic, | |||
| inconsistentEmbeddingModel, | |||
| } = getSelectedDatasetsMode(newDatasets) | |||
| const newInputs = produce(inputs, (draft) => { | |||
| draft.dataset_ids = newDatasets.map(d => d.id) | |||
| if (payload.retrieval_mode === RETRIEVE_TYPE.multiWay && newDatasets.length > 0) { | |||
| const multipleRetrievalConfig = draft.multiple_retrieval_config | |||
| draft.multiple_retrieval_config = getMultipleRetrievalConfig(multipleRetrievalConfig!, newDatasets) | |||
| } | |||
| }) | |||
| setInputs(newInputs) | |||
| setSelectedDatasets(newDatasets) | |||
| }, [inputs, setInputs]) | |||
| if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel) | |||
| setRerankModelOpen(true) | |||
| }, [inputs, setInputs, payload.retrieval_mode]) | |||
| const filterVar = useCallback((varPayload: Var) => { | |||
| return varPayload.type === VarType.string | |||
| @@ -266,6 +273,8 @@ const useConfig = (id: string, payload: KnowledgeRetrievalNodeType) => { | |||
| query, | |||
| setQuery, | |||
| runResult, | |||
| rerankModelOpen, | |||
| setRerankModelOpen, | |||
| } | |||
| } | |||
| @@ -1,5 +1,124 @@ | |||
| import type { KnowledgeRetrievalNodeType } from './types' | |||
| import { uniq } from 'lodash-es' | |||
| import type { MultipleRetrievalConfig } from './types' | |||
| import type { | |||
| DataSet, | |||
| SelectedDatasetsMode, | |||
| } from '@/models/datasets' | |||
| import { | |||
| DEFAULT_WEIGHTED_SCORE, | |||
| RerankingModeEnum, | |||
| WeightedScoreEnum, | |||
| } from '@/models/datasets' | |||
| import { RETRIEVE_METHOD } from '@/types/app' | |||
| import { DATASET_DEFAULT } from '@/config' | |||
| export const checkNodeValid = (payload: KnowledgeRetrievalNodeType) => { | |||
| export const checkNodeValid = () => { | |||
| return true | |||
| } | |||
| export const getSelectedDatasetsMode = (datasets: DataSet[]) => { | |||
| let allHighQuality = true | |||
| let allHighQualityVectorSearch = true | |||
| let allHighQualityFullTextSearch = true | |||
| let allEconomic = true | |||
| let mixtureHighQualityAndEconomic = true | |||
| let inconsistentEmbeddingModel = false | |||
| if (!datasets.length) { | |||
| allHighQuality = false | |||
| allHighQualityVectorSearch = false | |||
| allHighQualityFullTextSearch = false | |||
| allEconomic = false | |||
| mixtureHighQualityAndEconomic = false | |||
| inconsistentEmbeddingModel = false | |||
| } | |||
| datasets.forEach((dataset) => { | |||
| if (dataset.indexing_technique === 'economy') { | |||
| allHighQuality = false | |||
| allHighQualityVectorSearch = false | |||
| allHighQualityFullTextSearch = false | |||
| } | |||
| if (dataset.indexing_technique === 'high_quality') { | |||
| allEconomic = false | |||
| if (dataset.retrieval_model_dict.search_method !== RETRIEVE_METHOD.semantic) | |||
| allHighQualityVectorSearch = false | |||
| if (dataset.retrieval_model_dict.search_method !== RETRIEVE_METHOD.fullText) | |||
| allHighQualityFullTextSearch = false | |||
| } | |||
| }) | |||
| if (allHighQuality || allEconomic) | |||
| mixtureHighQualityAndEconomic = false | |||
| if (allHighQuality) | |||
| inconsistentEmbeddingModel = uniq(datasets.map(item => item.embedding_model)).length > 1 | |||
| return { | |||
| allHighQuality, | |||
| allHighQualityVectorSearch, | |||
| allHighQualityFullTextSearch, | |||
| allEconomic, | |||
| mixtureHighQualityAndEconomic, | |||
| inconsistentEmbeddingModel, | |||
| } as SelectedDatasetsMode | |||
| } | |||
| export const getMultipleRetrievalConfig = (multipleRetrievalConfig: MultipleRetrievalConfig, selectedDatasets: DataSet[]) => { | |||
| const { | |||
| allHighQuality, | |||
| allHighQualityVectorSearch, | |||
| allHighQualityFullTextSearch, | |||
| allEconomic, | |||
| mixtureHighQualityAndEconomic, | |||
| inconsistentEmbeddingModel, | |||
| } = getSelectedDatasetsMode(selectedDatasets) | |||
| const { | |||
| top_k = DATASET_DEFAULT.top_k, | |||
| score_threshold, | |||
| reranking_mode, | |||
| reranking_model, | |||
| weights, | |||
| reranking_enable, | |||
| } = multipleRetrievalConfig || { top_k: DATASET_DEFAULT.top_k } | |||
| const result = { | |||
| top_k, | |||
| score_threshold, | |||
| reranking_mode, | |||
| reranking_model, | |||
| weights, | |||
| reranking_enable, | |||
| } | |||
| if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel) | |||
| result.reranking_mode = RerankingModeEnum.RerankingModel | |||
| if (allHighQuality && !inconsistentEmbeddingModel && reranking_mode === undefined) | |||
| result.reranking_mode = RerankingModeEnum.WeightedScore | |||
| if (allHighQuality && !inconsistentEmbeddingModel && (reranking_mode === RerankingModeEnum.WeightedScore || reranking_mode === undefined) && !weights) { | |||
| result.weights = { | |||
| weight_type: WeightedScoreEnum.Customized, | |||
| vector_setting: { | |||
| vector_weight: allHighQualityVectorSearch | |||
| ? DEFAULT_WEIGHTED_SCORE.allHighQualityVectorSearch.semantic | |||
| : allHighQualityFullTextSearch | |||
| ? DEFAULT_WEIGHTED_SCORE.allHighQualityFullTextSearch.semantic | |||
| : DEFAULT_WEIGHTED_SCORE.other.semantic, | |||
| embedding_provider_name: selectedDatasets[0].embedding_model_provider, | |||
| embedding_model_name: selectedDatasets[0].embedding_model, | |||
| }, | |||
| keyword_setting: { | |||
| keyword_weight: allHighQualityVectorSearch | |||
| ? DEFAULT_WEIGHTED_SCORE.allHighQualityVectorSearch.keyword | |||
| : allHighQualityFullTextSearch | |||
| ? DEFAULT_WEIGHTED_SCORE.allHighQualityFullTextSearch.keyword | |||
| : DEFAULT_WEIGHTED_SCORE.other.keyword, | |||
| }, | |||
| } | |||
| } | |||
| return result | |||
| } | |||
| @@ -138,8 +138,8 @@ export const appDefaultIconBackground = '#D5F5F6' | |||
| export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList' | |||
| export const DATASET_DEFAULT = { | |||
| top_k: 2, | |||
| score_threshold: 0.5, | |||
| top_k: 4, | |||
| score_threshold: 0.8, | |||
| } | |||
| export const APP_PAGE_LIMIT = 10 | |||
| @@ -97,6 +97,8 @@ type IDebugConfiguration = { | |||
| isShowVisionConfig: boolean | |||
| visionConfig: VisionSettings | |||
| setVisionConfig: (visionConfig: VisionSettings, noNotice?: boolean) => void | |||
| rerankSettingModalOpen: boolean | |||
| setRerankSettingModalOpen: (rerankSettingModalOpen: boolean) => void | |||
| } | |||
| const DebugConfigurationContext = createContext<IDebugConfiguration>({ | |||
| @@ -217,7 +219,7 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({ | |||
| showSelectDataSet: () => { }, | |||
| setDataSets: () => { }, | |||
| datasetConfigs: { | |||
| retrieval_model: RETRIEVE_TYPE.oneWay, | |||
| retrieval_model: RETRIEVE_TYPE.multiWay, | |||
| reranking_model: { | |||
| reranking_provider_name: '', | |||
| reranking_model_name: '', | |||
| @@ -239,6 +241,8 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({ | |||
| transfer_methods: [TransferMethod.remote_url], | |||
| }, | |||
| setVisionConfig: () => { }, | |||
| rerankSettingModalOpen: false, | |||
| setRerankSettingModalOpen: () => { }, | |||
| }) | |||
| export const useDebugConfigurationContext = () => useContext(DebugConfigurationContext) | |||
| @@ -0,0 +1,29 @@ | |||
| import { useCallback } from 'react' | |||
| import { useTranslation } from 'react-i18next' | |||
| export const useKnowledge = () => { | |||
| const { t } = useTranslation() | |||
| const formatIndexingTechnique = useCallback((indexingTechnique: string) => { | |||
| return t(`dataset.indexingTechnique.${indexingTechnique}`) | |||
| }, [t]) | |||
| const formatIndexingMethod = useCallback((indexingMethod: string) => { | |||
| return t(`dataset.indexingMethod.${indexingMethod}`) | |||
| }, [t]) | |||
| const formatIndexingTechniqueAndMethod = useCallback((indexingTechnique: string, indexingMethod: string) => { | |||
| let result = formatIndexingTechnique(indexingTechnique) | |||
| if (indexingMethod) | |||
| result += ` · ${formatIndexingMethod(indexingMethod)}` | |||
| return result | |||
| }, [formatIndexingTechnique, formatIndexingMethod]) | |||
| return { | |||
| formatIndexingTechnique, | |||
| formatIndexingMethod, | |||
| formatIndexingTechniqueAndMethod, | |||
| } | |||
| } | |||
| @@ -45,6 +45,29 @@ const translation = { | |||
| }, | |||
| docsFailedNotice: 'documents failed to be indexed', | |||
| retry: 'Retry', | |||
| indexingTechnique: { | |||
| high_quality: 'HQ', | |||
| economy: 'ECO', | |||
| }, | |||
| indexingMethod: { | |||
| semantic_search: 'VECTOR', | |||
| full_text_search: 'FULL TEXT', | |||
| hybrid_search: 'HYBRID', | |||
| }, | |||
| mixtureHighQualityAndEconomicTip: 'The Rerank model is required for mixture of high quality and economical knowledge bases.', | |||
| inconsistentEmbeddingModelTip: 'The Rerank model is required if the Embedding models of the selected knowledge bases are inconsistent.', | |||
| retrievalSettings: 'Retrieval Setting', | |||
| rerankSettings: 'Rerank Setting', | |||
| weightedScore: { | |||
| title: 'Weighted Score', | |||
| description: 'By adjusting the weights assigned, this rerank strategy determines whether to prioritize semantic or keyword matching.', | |||
| semanticFirst: 'Semantic first', | |||
| keywordFirst: 'Keyword first', | |||
| customized: 'Customized', | |||
| semantic: 'Semantic', | |||
| keyword: 'Keyword', | |||
| }, | |||
| nTo1RetrievalLegacy: 'According to product planning, N-to-1 retrieval will be officially deprecated in September. Until then you can still use it normally.', | |||
| } | |||
| export default translation | |||
| @@ -45,6 +45,29 @@ const translation = { | |||
| }, | |||
| docsFailedNotice: '文档无法被索引', | |||
| retry: '重试', | |||
| indexingTechnique: { | |||
| high_quality: '高质量', | |||
| economy: '经济', | |||
| }, | |||
| indexingMethod: { | |||
| semantic_search: '向量检索', | |||
| full_text_search: '全文检索', | |||
| hybrid_search: '混合检索', | |||
| }, | |||
| mixtureHighQualityAndEconomicTip: '混合使用高质量和经济型知识库需要配置 Rerank 模型。', | |||
| inconsistentEmbeddingModelTip: '当所选知识库配置的 Embedding 模型不一致时,需要配置 Rerank 模型。', | |||
| retrievalSettings: '召回设置', | |||
| rerankSettings: 'Rerank 设置', | |||
| weightedScore: { | |||
| title: '权重设置', | |||
| description: '通过调整分配的权重,重新排序策略确定是优先进行语义匹配还是关键字匹配。', | |||
| semanticFirst: '语义优先', | |||
| keywordFirst: '关键词优先', | |||
| customized: '自定义', | |||
| semantic: '语义', | |||
| keyword: '关键词', | |||
| }, | |||
| nTo1RetrievalLegacy: '根据产品规划,N 选 1 召回将于 9 月正式弃用。在那之前,您仍然可以正常使用它。', | |||
| } | |||
| export default translation | |||
| @@ -449,3 +449,46 @@ export type ErrorDocsResponse = { | |||
| data: IndexingStatusResponse[] | |||
| total: number | |||
| } | |||
| export type SelectedDatasetsMode = { | |||
| allHighQuality: boolean | |||
| allHighQualityVectorSearch: boolean | |||
| allHighQualityFullTextSearch: boolean | |||
| allEconomic: boolean | |||
| mixtureHighQualityAndEconomic: boolean | |||
| inconsistentEmbeddingModel: boolean | |||
| } | |||
| export enum WeightedScoreEnum { | |||
| SemanticFirst = 'semantic_first', | |||
| KeywordFirst = 'keyword_first', | |||
| Customized = 'customized', | |||
| } | |||
| export enum RerankingModeEnum { | |||
| RerankingModel = 'reranking_model', | |||
| WeightedScore = 'weighted_score', | |||
| } | |||
| export const DEFAULT_WEIGHTED_SCORE = { | |||
| allHighQualityVectorSearch: { | |||
| semantic: 1.0, | |||
| keyword: 0, | |||
| }, | |||
| allHighQualityFullTextSearch: { | |||
| semantic: 0, | |||
| keyword: 1.0, | |||
| }, | |||
| semanticFirst: { | |||
| semantic: 0.7, | |||
| keyword: 0.3, | |||
| }, | |||
| keywordFirst: { | |||
| semantic: 0.3, | |||
| keyword: 0.7, | |||
| }, | |||
| other: { | |||
| semantic: 0.7, | |||
| keyword: 0.3, | |||
| }, | |||
| } | |||
| @@ -1,4 +1,8 @@ | |||
| import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem, TtsAutoPlay } from '@/types/app' | |||
| import type { | |||
| RerankingModeEnum, | |||
| WeightedScoreEnum, | |||
| } from '@/models/datasets' | |||
| export type Inputs = Record<string, string | number | object> | |||
| export enum PromptMode { | |||
| @@ -144,13 +148,26 @@ export type DatasetConfigs = { | |||
| } | |||
| top_k: number | |||
| score_threshold_enabled: boolean | |||
| score_threshold?: number | null | |||
| score_threshold: number | null | undefined | |||
| datasets: { | |||
| datasets: { | |||
| enabled: boolean | |||
| id: string | |||
| }[] | |||
| } | |||
| reranking_mode?: RerankingModeEnum | |||
| weights?: { | |||
| weight_type: WeightedScoreEnum | |||
| vector_setting: { | |||
| vector_weight: number | |||
| embedding_provider_name: string | |||
| embedding_model_name: string | |||
| } | |||
| keyword_setting: { | |||
| keyword_weight: number | |||
| } | |||
| } | |||
| reranking_enable?: boolean | |||
| } | |||
| export type DebugRequestBody = { | |||
| @@ -2,6 +2,10 @@ import type { AnnotationReplyConfig, ChatPromptConfig, CompletionPromptConfig, D | |||
| import type { CollectionType } from '@/app/components/tools/types' | |||
| import type { LanguagesSupported } from '@/i18n/language' | |||
| import type { Tag } from '@/app/components/base/tag-management/constant' | |||
| import type { | |||
| RerankingModeEnum, | |||
| WeightedScoreEnum, | |||
| } from '@/models/datasets' | |||
| export enum Theme { | |||
| light = 'light', | |||
| @@ -403,4 +407,16 @@ export type RetrievalConfig = { | |||
| top_k: number | |||
| score_threshold_enabled: boolean | |||
| score_threshold: number | |||
| reranking_mode?: RerankingModeEnum | |||
| weights?: { | |||
| weight_type: WeightedScoreEnum | |||
| vector_setting: { | |||
| vector_weight: number | |||
| embedding_provider_name: string | |||
| embedding_model_name: string | |||
| } | |||
| keyword_setting: { | |||
| keyword_weight: number | |||
| } | |||
| } | |||
| } | |||