| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React, { useState } 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 SettingsModal from '../settings-modal' | ||||
| import type { DataSet } from '@/models/datasets' | import type { DataSet } from '@/models/datasets' | ||||
| import { DataSourceType } from '@/models/datasets' | import { DataSourceType } from '@/models/datasets' | ||||
| import { formatNumber } from '@/utils/format' | |||||
| import FileIcon from '@/app/components/base/file-icon' | 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 { Folder } from '@/app/components/base/icons/src/vender/solid/files' | ||||
| import { Globe06 } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' | import { Globe06 } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' | ||||
| import Drawer from '@/app/components/base/drawer' | import Drawer from '@/app/components/base/drawer' | ||||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | ||||
| import Badge from '@/app/components/base/badge' | |||||
| import { useKnowledge } from '@/hooks/use-knowledge' | |||||
| type ItemProps = { | type ItemProps = { | ||||
| className?: string | className?: string | ||||
| onSave, | onSave, | ||||
| onRemove, | onRemove, | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation() | |||||
| const media = useBreakpoints() | const media = useBreakpoints() | ||||
| const isMobile = media === MediaType.mobile | const isMobile = media === MediaType.mobile | ||||
| const [showSettingsModal, setShowSettingsModal] = useState(false) | const [showSettingsModal, setShowSettingsModal] = useState(false) | ||||
| const { formatIndexingTechniqueAndMethod } = useKnowledge() | |||||
| const handleSave = (newDataset: DataSet) => { | const handleSave = (newDataset: DataSet) => { | ||||
| onSave(newDataset) | onSave(newDataset) | ||||
| <div className='grow'> | <div className='grow'> | ||||
| <div className='flex items-center h-[18px]'> | <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='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> | </div> | ||||
| {/* { | |||||
| config.description && ( | |||||
| <div className='text-xs text-gray-500'>{config.description}</div> | |||||
| ) | |||||
| } */} | |||||
| </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='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 | <div | ||||
| className='flex items-center justify-center mr-1 w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | className='flex items-center justify-center mr-1 w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | ||||
| onClick={() => setShowSettingsModal(true)} | onClick={() => setShowSettingsModal(true)} | ||||
| > | > | ||||
| <Settings01 className='w-4 h-4 text-gray-500' /> | |||||
| <RiEditLine className='w-4 h-4 text-gray-500' /> | |||||
| </div> | </div> | ||||
| <div | <div | ||||
| className='group/action flex items-center justify-center w-6 h-6 hover:bg-[#FEE4E2] rounded-md cursor-pointer' | className='group/action flex items-center justify-center w-6 h-6 hover:bg-[#FEE4E2] rounded-md cursor-pointer' |
| const handleSave = (newDataset: DataSet) => { | const handleSave = (newDataset: DataSet) => { | ||||
| const index = dataSet.findIndex(item => item.id === newDataset.id) | 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() | formattingChangedDispatcher() | ||||
| } | } | ||||
| title={t('appDebug.feature.dataSet.title')} | title={t('appDebug.feature.dataSet.title')} | ||||
| headerRight={ | headerRight={ | ||||
| <div className='flex items-center gap-1'> | <div className='flex items-center gap-1'> | ||||
| {!isAgent && <ParamsConfig />} | |||||
| {!isAgent && <ParamsConfig disabled={!hasData} selectedDatasets={dataSet} />} | |||||
| <OperationBtn type="add" onClick={showSelectDataSet} /> | <OperationBtn type="add" onClick={showSelectDataSet} /> | ||||
| </div> | </div> | ||||
| } | } |
| 'use client' | 'use client' | ||||
| import React from 'react' | |||||
| import { memo, useMemo } from 'react' | |||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { | import { | ||||
| RiQuestionLine, | RiQuestionLine, | ||||
| } from '@remixicon/react' | } from '@remixicon/react' | ||||
| import WeightedScore from './weighted-score' | |||||
| import TopKItem from '@/app/components/base/param-item/top-k-item' | import TopKItem from '@/app/components/base/param-item/top-k-item' | ||||
| import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item' | import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item' | ||||
| import RadioCard from '@/app/components/base/radio-card/simple' | import RadioCard from '@/app/components/base/radio-card/simple' | ||||
| import type { | import type { | ||||
| DatasetConfigs, | DatasetConfigs, | ||||
| } from '@/models/debug' | } from '@/models/debug' | ||||
| import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' | 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 { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | ||||
| import type { ModelConfig } from '@/app/components/workflow/types' | import type { ModelConfig } from '@/app/components/workflow/types' | ||||
| import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' | import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' | ||||
| import TooltipPlus from '@/app/components/base/tooltip-plus' | import TooltipPlus from '@/app/components/base/tooltip-plus' | ||||
| import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | 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 = { | type Props = { | ||||
| datasetConfigs: DatasetConfigs | datasetConfigs: DatasetConfigs | ||||
| singleRetrievalModelConfig?: ModelConfig | singleRetrievalModelConfig?: ModelConfig | ||||
| onSingleRetrievalModelChange?: (config: ModelConfig) => void | onSingleRetrievalModelChange?: (config: ModelConfig) => void | ||||
| onSingleRetrievalModelParamsChange?: (config: ModelConfig) => void | onSingleRetrievalModelParamsChange?: (config: ModelConfig) => void | ||||
| selectedDatasets?: DataSet[] | |||||
| } | } | ||||
| const ConfigContent: FC<Props> = ({ | const ConfigContent: FC<Props> = ({ | ||||
| singleRetrievalModelConfig: singleRetrievalConfig = {} as ModelConfig, | singleRetrievalModelConfig: singleRetrievalConfig = {} as ModelConfig, | ||||
| onSingleRetrievalModelChange = () => { }, | onSingleRetrievalModelChange = () => { }, | ||||
| onSingleRetrievalModelParamsChange = () => { }, | onSingleRetrievalModelParamsChange = () => { }, | ||||
| selectedDatasets = [], | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const selectedDatasetsMode = useSelectedDatasetsMode(selectedDatasets) | |||||
| const type = datasetConfigs.retrieval_model | const type = datasetConfigs.retrieval_model | ||||
| const setType = (value: RETRIEVE_TYPE) => { | const setType = (value: RETRIEVE_TYPE) => { | ||||
| onChange({ | onChange({ | ||||
| defaultModel: rerankDefaultModel, | defaultModel: rerankDefaultModel, | ||||
| } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) | } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) | ||||
| const rerankModel = (() => { | const rerankModel = (() => { | ||||
| if (datasetConfigs.reranking_model) { | |||||
| if (datasetConfigs.reranking_model?.reranking_provider_name) { | |||||
| return { | return { | ||||
| provider_name: datasetConfigs.reranking_model.reranking_provider_name, | provider_name: datasetConfigs.reranking_model.reranking_provider_name, | ||||
| model_name: datasetConfigs.reranking_model.reranking_model_name, | model_name: datasetConfigs.reranking_model.reranking_model_name, | ||||
| }) | }) | ||||
| } | } | ||||
| 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 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 ( | return ( | ||||
| <div> | <div> | ||||
| <div className='system-xl-semibold text-text-primary'>{t('dataset.retrievalSettings')}</div> | |||||
| <div className='mt-2 space-y-3'> | <div className='mt-2 space-y-3'> | ||||
| <RadioCard | <RadioCard | ||||
| icon={<NTo1Retrieval className='shrink-0 mr-3 w-9 h-9 rounded-lg' />} | 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')} | description={t('appDebug.datasetConfig.retrieveOneWay.description')} | ||||
| isChosen={type === RETRIEVE_TYPE.oneWay} | isChosen={type === RETRIEVE_TYPE.oneWay} | ||||
| onChosen={() => { setType(RETRIEVE_TYPE.oneWay) }} | onChosen={() => { setType(RETRIEVE_TYPE.oneWay) }} | ||||
| </div> | </div> | ||||
| {type === RETRIEVE_TYPE.multiWay && ( | {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> | </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 && ( | {isInWorkflow && type === RETRIEVE_TYPE.oneWay && ( | ||||
| <div className='mt-6'> | |||||
| <div className='mt-4'> | |||||
| <div className='flex items-center space-x-0.5'> | <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> | <div className='leading-[32px] text-[13px] font-medium text-gray-900'>{t('common.modelProvider.systemReasoningModel.key')}</div> | ||||
| <TooltipPlus | <TooltipPlus | ||||
| </div > | </div > | ||||
| ) | ) | ||||
| } | } | ||||
| export default React.memo(ConfigContent) | |||||
| export default memo(ConfigContent) |
| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | |||||
| import { memo, useState } from 'react' | |||||
| import { memo, useEffect, useState } from 'react' | |||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { useContext } from 'use-context-selector' | import { useContext } from 'use-context-selector' | ||||
| import { RiEqualizer2Line } from '@remixicon/react' | |||||
| import ConfigContent from './config-content' | import ConfigContent from './config-content' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { Settings04 } from '@/app/components/base/icons/src/vender/line/general' | |||||
| import ConfigContext from '@/context/debug-configuration' | import ConfigContext from '@/context/debug-configuration' | ||||
| import Modal from '@/app/components/base/modal' | import Modal from '@/app/components/base/modal' | ||||
| import Button from '@/app/components/base/button' | import Button from '@/app/components/base/button' | ||||
| import { RETRIEVE_TYPE } from '@/types/app' | import { RETRIEVE_TYPE } from '@/types/app' | ||||
| import Toast from '@/app/components/base/toast' | 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 { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | ||||
| import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | 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 { t } = useTranslation() | ||||
| const [open, setOpen] = useState(false) | |||||
| const { | const { | ||||
| datasetConfigs, | datasetConfigs, | ||||
| setDatasetConfigs, | setDatasetConfigs, | ||||
| rerankSettingModalOpen, | |||||
| setRerankSettingModalOpen, | |||||
| } = useContext(ConfigContext) | } = useContext(ConfigContext) | ||||
| const [tempDataSetConfigs, setTempDataSetConfigs] = useState(datasetConfigs) | 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 { | const { | ||||
| defaultModel: rerankDefaultModel, | defaultModel: rerankDefaultModel, | ||||
| currentModel: isRerankDefaultModelVaild, | currentModel: isRerankDefaultModelVaild, | ||||
| } as any | } as any | ||||
| } | } | ||||
| setDatasetConfigs(config) | 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 ( | return ( | ||||
| <div> | <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={() => { | 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 | <Modal | ||||
| isShow={open} | |||||
| isShow={rerankSettingModalOpen} | |||||
| onClose={() => { | onClose={() => { | ||||
| setOpen(false) | |||||
| setRerankSettingModalOpen(false) | |||||
| }} | }} | ||||
| className='sm:min-w-[528px]' | className='sm:min-w-[528px]' | ||||
| title={t('appDebug.datasetConfig.settingTitle')} | |||||
| > | > | ||||
| <ConfigContent | <ConfigContent | ||||
| datasetConfigs={tempDataSetConfigs} | datasetConfigs={tempDataSetConfigs} | ||||
| onChange={setTempDataSetConfigs} | |||||
| onChange={handleSetTempDataSetConfigs} | |||||
| selectedDatasets={selectedDatasets} | |||||
| /> | /> | ||||
| <div className='mt-6 flex justify-end'> | <div className='mt-6 flex justify-end'> | ||||
| <Button className='mr-2 flex-shrink-0' onClick={() => { | <Button className='mr-2 flex-shrink-0' onClick={() => { | ||||
| setOpen(false) | |||||
| setRerankSettingModalOpen(false) | |||||
| }}>{t('common.operation.cancel')}</Button> | }}>{t('common.operation.cancel')}</Button> | ||||
| <Button variant='primary' className='flex-shrink-0' onClick={handleSave} >{t('common.operation.save')}</Button> | <Button variant='primary' className='flex-shrink-0' onClick={handleSave} >{t('common.operation.save')}</Button> | ||||
| </div> | </div> |
| 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) |
| import Button from '@/app/components/base/button' | import Button from '@/app/components/base/button' | ||||
| import { fetchDatasets } from '@/service/datasets' | import { fetchDatasets } from '@/service/datasets' | ||||
| import Loading from '@/app/components/base/loading' | 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 = { | export type ISelectDataSetProps = { | ||||
| isShow: boolean | isShow: boolean | ||||
| const listRef = useRef<HTMLDivElement>(null) | const listRef = useRef<HTMLDivElement>(null) | ||||
| const [page, setPage, getPage] = useGetState(1) | const [page, setPage, getPage] = useGetState(1) | ||||
| const [isNoMore, setIsNoMore] = useState(false) | const [isNoMore, setIsNoMore] = useState(false) | ||||
| const { formatIndexingTechniqueAndMethod } = useKnowledge() | |||||
| useInfiniteScroll( | useInfiniteScroll( | ||||
| async () => { | async () => { | ||||
| const { data, has_more } = await fetchDatasets({ url: '/datasets', params: { page } }) | const { data, has_more } = await fetchDatasets({ url: '/datasets', params: { page } }) | ||||
| setPage(getPage() + 1) | setPage(getPage() + 1) | ||||
| setIsNoMore(!has_more) | setIsNoMore(!has_more) | ||||
| const newList = [...(datasets || []), ...data] | |||||
| const newList = [...(datasets || []), ...data.filter(item => item.indexing_technique)] | |||||
| setDataSets(newList) | setDataSets(newList) | ||||
| setLoaded(true) | setLoaded(true) | ||||
| if (!selected.find(item => !item.name)) | if (!selected.find(item => !item.name)) | ||||
| <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> | <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> | ||||
| <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> | ||||
| ))} | ))} | ||||
| </div> | </div> |
| {/* Retrieval Method Config */} | {/* Retrieval Method Config */} | ||||
| <div className={rowClass}> | <div className={rowClass}> | ||||
| <div className={labelClass}> | |||||
| <div className={cn(labelClass, 'w-auto min-w-[168px]')}> | |||||
| <div> | <div> | ||||
| <div>{t('datasetSettings.form.retrievalSetting.title')}</div> | <div>{t('datasetSettings.form.retrievalSetting.title')}</div> | ||||
| <div className='leading-[18px] text-xs font-normal text-gray-500'> | <div className='leading-[18px] text-xs font-normal text-gray-500'> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <div className='w-[480px]'> | |||||
| <div> | |||||
| {indexMethod === 'high_quality' | {indexMethod === 'high_quality' | ||||
| ? ( | ? ( | ||||
| <RetrievalMethodConfig | <RetrievalMethodConfig |
| import { useProviderContext } from '@/context/provider-context' | import { useProviderContext } from '@/context/provider-context' | ||||
| import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app' | import { AgentStrategy, AppType, ModelModeType, RETRIEVE_TYPE, Resolution, TransferMethod } from '@/types/app' | ||||
| import { PromptMode } from '@/models/debug' | 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 SelectDataSet from '@/app/components/app/configuration/dataset-config/select-dataset' | ||||
| import { useModalContext } from '@/context/modal-context' | import { useModalContext } from '@/context/modal-context' | ||||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | ||||
| import { fetchCollectionList } from '@/service/tools' | import { fetchCollectionList } from '@/service/tools' | ||||
| import { type Collection } from '@/app/components/tools/types' | import { type Collection } from '@/app/components/tools/types' | ||||
| import { useStore as useAppStore } from '@/app/components/app/store' | import { useStore as useAppStore } from '@/app/components/app/store' | ||||
| import { | |||||
| getMultipleRetrievalConfig, | |||||
| getSelectedDatasetsMode, | |||||
| } from '@/app/components/workflow/nodes/knowledge-retrieval/utils' | |||||
| type PublishConfig = { | type PublishConfig = { | ||||
| modelConfig: ModelConfig | modelConfig: ModelConfig | ||||
| }, []) | }, []) | ||||
| const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>({ | const [datasetConfigs, setDatasetConfigs] = useState<DatasetConfigs>({ | ||||
| retrieval_model: RETRIEVE_TYPE.oneWay, | |||||
| retrieval_model: RETRIEVE_TYPE.multiWay, | |||||
| reranking_model: { | reranking_model: { | ||||
| reranking_provider_name: '', | reranking_provider_name: '', | ||||
| reranking_model_name: '', | reranking_model_name: '', | ||||
| }, | }, | ||||
| top_k: 2, | |||||
| top_k: DATASET_DEFAULT.top_k, | |||||
| score_threshold_enabled: false, | score_threshold_enabled: false, | ||||
| score_threshold: 0.7, | |||||
| score_threshold: DATASET_DEFAULT.score_threshold, | |||||
| datasets: { | datasets: { | ||||
| datasets: [], | datasets: [], | ||||
| }, | }, | ||||
| const hasSetContextVar = !!contextVar | const hasSetContextVar = !!contextVar | ||||
| const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false) | const [isShowSelectDataSet, { setTrue: showSelectDataSet, setFalse: hideSelectDataSet }] = useBoolean(false) | ||||
| const selectedIds = dataSets.map(item => item.id) | const selectedIds = dataSets.map(item => item.id) | ||||
| const [rerankSettingModalOpen, setRerankSettingModalOpen] = useState(false) | |||||
| const handleSelect = (data: DataSet[]) => { | const handleSelect = (data: DataSet[]) => { | ||||
| if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) { | if (isEqual(data.map(item => item.id), dataSets.map(item => item.id))) { | ||||
| hideSelectDataSet() | hideSelectDataSet() | ||||
| } | } | ||||
| formattingChangedDispatcher() | formattingChangedDispatcher() | ||||
| let newDatasets = data | |||||
| if (data.find(item => !item.name)) { // has not loaded selected dataset | if (data.find(item => !item.name)) { // has not loaded selected dataset | ||||
| const newSelected = produce(data, (draft: any) => { | const newSelected = produce(data, (draft: any) => { | ||||
| data.forEach((item, index) => { | data.forEach((item, index) => { | ||||
| }) | }) | ||||
| }) | }) | ||||
| setDataSets(newSelected) | setDataSets(newSelected) | ||||
| newDatasets = newSelected | |||||
| } | } | ||||
| else { | else { | ||||
| setDataSets(data) | setDataSets(data) | ||||
| } | } | ||||
| hideSelectDataSet() | 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) | const [isShowHistoryModal, { setTrue: showHistoryModal, setFalse: hideHistoryModal }] = useBoolean(false) | ||||
| syncToPublishedConfig(config) | syncToPublishedConfig(config) | ||||
| setPublishedConfig(config) | setPublishedConfig(config) | ||||
| setDatasetConfigs({ | setDatasetConfigs({ | ||||
| retrieval_model: RETRIEVE_TYPE.oneWay, | |||||
| retrieval_model: RETRIEVE_TYPE.multiWay, | |||||
| ...modelConfig.dataset_configs, | ...modelConfig.dataset_configs, | ||||
| }) | }) | ||||
| setHasFetchedDetail(true) | setHasFetchedDetail(true) | ||||
| isShowVisionConfig, | isShowVisionConfig, | ||||
| visionConfig, | visionConfig, | ||||
| setVisionConfig: handleSetVisionConfig, | setVisionConfig: handleSetVisionConfig, | ||||
| rerankSettingModalOpen, | |||||
| setRerankSettingModalOpen, | |||||
| }} | }} | ||||
| > | > | ||||
| <> | <> |
| 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) |
| @layer components { | @layer components { | ||||
| .btn { | .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 { | .btn-disabled { | ||||
| @apply opacity-60 cursor-not-allowed; | |||||
| @apply cursor-not-allowed; | |||||
| } | } | ||||
| .btn-small { | .btn-small { | ||||
| @apply px-2 h-6 rounded-md text-xs | |||||
| @apply px-2 h-6 rounded-md text-xs font-medium; | |||||
| } | } | ||||
| .btn-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 { | .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 { | .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 { | .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 { | .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 { | .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; | |||||
| } | } | ||||
| } | } |
| 'secondary': 'btn-secondary', | 'secondary': 'btn-secondary', | ||||
| 'secondary-accent': 'btn-secondary-accent', | 'secondary-accent': 'btn-secondary-accent', | ||||
| 'ghost': 'btn-ghost', | 'ghost': 'btn-ghost', | ||||
| 'ghost-accent': 'btn-ghost-accent', | |||||
| 'tertiary': 'btn-tertiary', | 'tertiary': 'btn-tertiary', | ||||
| }, | }, | ||||
| size: { | size: { | ||||
| ) | ) | ||||
| export type ButtonProps = { | export type ButtonProps = { | ||||
| destructive?: boolean | |||||
| loading?: boolean | loading?: boolean | ||||
| styleCss?: CSSProperties | styleCss?: CSSProperties | ||||
| } & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> | } & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> | ||||
| const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( | const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( | ||||
| ({ className, variant, size, loading, styleCss, children, ...props }, ref) => { | |||||
| ({ className, variant, size, destructive, loading, styleCss, children, ...props }, ref) => { | |||||
| return ( | return ( | ||||
| <button | <button | ||||
| type='button' | type='button' | ||||
| className={classNames(buttonVariants({ variant, size, className }))} | |||||
| className={classNames( | |||||
| buttonVariants({ variant, size, className }), | |||||
| destructive && 'btn-destructive', | |||||
| )} | |||||
| ref={ref} | ref={ref} | ||||
| style={styleCss} | style={styleCss} | ||||
| {...props} | {...props} |
| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React from 'react' | import React from 'react' | ||||
| import s from './style.module.css' | |||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| type Props = { | type Props = { | ||||
| chosenConfigWrapClassName, | chosenConfigWrapClassName, | ||||
| }) => { | }) => { | ||||
| return ( | 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='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')}> | <div className={cn(iconBgClassName, 'mr-3 shrink-0 flex w-8 justify-center h-8 items-center rounded-lg')}> | ||||
| {icon} | {icon} | ||||
| </div> | </div> | ||||
| {!noRadio && ( | {!noRadio && ( | ||||
| <div className='shrink-0 flex items-center h-8'> | <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> | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| {((isChosen && chosenConfig) || noRadio) && ( | {((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} | {chosenConfig} | ||||
| </div> | </div> | ||||
| )} | )} |
| type Props = { | type Props = { | ||||
| className?: string | className?: string | ||||
| title: string | |||||
| title: string | JSX.Element | null | |||||
| description: string | description: string | ||||
| isChosen: boolean | isChosen: boolean | ||||
| onChosen: () => void | onChosen: () => void |
| .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; | |||||
| } |
| type ISliderProps = { | type ISliderProps = { | ||||
| className?: string | className?: string | ||||
| thumbClassName?: string | |||||
| trackClassName?: string | |||||
| value: number | value: number | ||||
| max?: number | max?: number | ||||
| min?: number | min?: number | ||||
| onChange: (value: number) => void | 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 | return <ReactSlider | ||||
| disabled={disabled} | disabled={disabled} | ||||
| value={isNaN(value) ? 0 : value} | value={isNaN(value) ? 0 : value} | ||||
| min={min || 0} | min={min || 0} | ||||
| max={max || 100} | max={max || 100} | ||||
| step={step || 1} | 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} | onChange={onChange} | ||||
| /> | /> | ||||
| } | } |
| DefaultModelResponse, | DefaultModelResponse, | ||||
| Model, | Model, | ||||
| } from '@/app/components/header/account-setting/model-provider-page/declarations' | } from '@/app/components/header/account-setting/model-provider-page/declarations' | ||||
| import { RerankingModeEnum } from '@/models/datasets' | |||||
| export const isReRankModelSelected = ({ | export const isReRankModelSelected = ({ | ||||
| rerankDefaultModel, | rerankDefaultModel, | ||||
| if ( | if ( | ||||
| indexMethod === 'high_quality' | indexMethod === 'high_quality' | ||||
| && (retrievalConfig.reranking_enable || retrievalConfig.search_method === RETRIEVE_METHOD.hybrid) | |||||
| && (retrievalConfig.search_method === RETRIEVE_METHOD.hybrid && retrievalConfig.reranking_mode !== RerankingModeEnum.WeightedScore) | |||||
| && !rerankModelSelected | && !rerankModelSelected | ||||
| ) | ) | ||||
| return false | return false |
| import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' | 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 { useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | ||||
| import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' | 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 Props = { | ||||
| type: RETRIEVE_METHOD | type: RETRIEVE_METHOD | ||||
| defaultModel: rerankDefaultModel, | defaultModel: rerankDefaultModel, | ||||
| modelList: rerankModelList, | modelList: rerankModelList, | ||||
| } = useModelListAndDefaultModel(ModelTypeEnum.rerank) | } = useModelListAndDefaultModel(ModelTypeEnum.rerank) | ||||
| const isHybridSearch = type === RETRIEVE_METHOD.hybrid | |||||
| const rerankModel = (() => { | const rerankModel = (() => { | ||||
| if (value.reranking_model) { | if (value.reranking_model) { | ||||
| } | } | ||||
| })() | })() | ||||
| 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 ( | return ( | ||||
| <div> | <div> | ||||
| {!isEconomical && ( | |||||
| {!isEconomical && !isHybridSearch && ( | |||||
| <div> | <div> | ||||
| <div className='flex h-8 items-center text-[13px] font-medium text-gray-900 space-x-2'> | <div className='flex h-8 items-center text-[13px] font-medium text-gray-900 space-x-2'> | ||||
| {canToggleRerankModalEnable && ( | {canToggleRerankModalEnable && ( | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <ModelSelector | <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 }} | defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }} | ||||
| modelList={rerankModelList} | modelList={rerankModelList} | ||||
| readonly={!value.reranking_enable && type !== RETRIEVE_METHOD.hybrid} | |||||
| readonly={!value.reranking_enable} | |||||
| onSelect={(v) => { | onSelect={(v) => { | ||||
| onChange({ | onChange({ | ||||
| ...value, | ...value, | ||||
| /> | /> | ||||
| </div> | </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> | </div> | ||||
| ) | ) | ||||
| } | } |
| retrievalConfig, | retrievalConfig, | ||||
| indexMethod, | 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 { | try { | ||||
| setLoading(true) | setLoading(true) | ||||
| const requestParams = { | const requestParams = { |
| return null | return null | ||||
| const parser = new DOMParser() | const parser = new DOMParser() | ||||
| const doc = parser.parseFromString(svg, 'image/svg+xml') | const doc = parser.parseFromString(svg, 'image/svg+xml') | ||||
| console.log(doc.documentElement) | |||||
| return doc.documentElement | return doc.documentElement | ||||
| } | } | ||||
| useMount(() => { | useMount(() => { |
| import { useBoolean } from 'ahooks' | import { useBoolean } from 'ahooks' | ||||
| import { | import { | ||||
| RiDeleteBinLine, | RiDeleteBinLine, | ||||
| RiEditLine, | |||||
| } from '@remixicon/react' | } from '@remixicon/react' | ||||
| import type { DataSet } from '@/models/datasets' | import type { DataSet } from '@/models/datasets' | ||||
| import { DataSourceType } 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 FileIcon from '@/app/components/base/file-icon' | ||||
| import { Folder } from '@/app/components/base/icons/src/vender/solid/files' | import { Folder } from '@/app/components/base/icons/src/vender/solid/files' | ||||
| import SettingsModal from '@/app/components/app/configuration/dataset-config/settings-modal' | import SettingsModal from '@/app/components/app/configuration/dataset-config/settings-modal' | ||||
| import Drawer from '@/app/components/base/drawer' | import Drawer from '@/app/components/base/drawer' | ||||
| import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' | ||||
| import Badge from '@/app/components/base/badge' | |||||
| import { useKnowledge } from '@/hooks/use-knowledge' | |||||
| type Props = { | type Props = { | ||||
| payload: DataSet | payload: DataSet | ||||
| }) => { | }) => { | ||||
| const media = useBreakpoints() | const media = useBreakpoints() | ||||
| const isMobile = media === MediaType.mobile | const isMobile = media === MediaType.mobile | ||||
| const { formatIndexingTechniqueAndMethod } = useKnowledge() | |||||
| const [isShowSettingsModal, { | const [isShowSettingsModal, { | ||||
| setTrue: showSettingsModal, | setTrue: showSettingsModal, | ||||
| className='flex items-center justify-center w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | className='flex items-center justify-center w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | ||||
| onClick={showSettingsModal} | onClick={showSettingsModal} | ||||
| > | > | ||||
| <Settings01 className='w-4 h-4 text-gray-500' /> | |||||
| <RiEditLine className='w-4 h-4 text-gray-500' /> | |||||
| </div> | </div> | ||||
| <div | <div | ||||
| className='flex items-center justify-center w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | className='flex items-center justify-center w-6 h-6 hover:bg-black/5 rounded-md cursor-pointer' | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| )} | )} | ||||
| <Badge | |||||
| className='group-hover/dataset-item:hidden shrink-0' | |||||
| text={formatIndexingTechniqueAndMethod(payload.indexing_technique, payload.retrieval_model_dict?.search_method)} | |||||
| /> | |||||
| {isShowSettingsModal && ( | {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'> | <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'> |
| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React, { useCallback, useState } from 'react' | import React, { useCallback, useState } from 'react' | ||||
| import { RiEqualizer2Line } from '@remixicon/react' | |||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { RiArrowDownSLine } from '@remixicon/react' | |||||
| import type { MultipleRetrievalConfig, SingleRetrievalConfig } from '../types' | import type { MultipleRetrievalConfig, SingleRetrievalConfig } from '../types' | ||||
| import type { ModelConfig } from '../../../types' | import type { ModelConfig } from '../../../types' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { DATASET_DEFAULT } from '@/config' | import { DATASET_DEFAULT } from '@/config' | ||||
| import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' | 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 { 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 = { | type Props = { | ||||
| payload: { | payload: { | ||||
| onSingleRetrievalModelChange?: (config: ModelConfig) => void | onSingleRetrievalModelChange?: (config: ModelConfig) => void | ||||
| onSingleRetrievalModelParamsChange?: (config: ModelConfig) => void | onSingleRetrievalModelParamsChange?: (config: ModelConfig) => void | ||||
| readonly?: boolean | readonly?: boolean | ||||
| openFromProps?: boolean | |||||
| onOpenFromPropsChange?: (openFromProps: boolean) => void | |||||
| selectedDatasets: DataSet[] | |||||
| } | } | ||||
| const RetrievalConfig: FC<Props> = ({ | const RetrievalConfig: FC<Props> = ({ | ||||
| onSingleRetrievalModelChange, | onSingleRetrievalModelChange, | ||||
| onSingleRetrievalModelParamsChange, | onSingleRetrievalModelParamsChange, | ||||
| readonly, | readonly, | ||||
| openFromProps, | |||||
| onOpenFromPropsChange, | |||||
| selectedDatasets, | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const [open, setOpen] = useState(false) | const [open, setOpen] = useState(false) | ||||
| const mergedOpen = openFromProps !== undefined ? openFromProps : open | |||||
| const handleOpen = useCallback((newOpen: boolean) => { | |||||
| setOpen(newOpen) | |||||
| onOpenFromPropsChange?.(newOpen) | |||||
| }, [onOpenFromPropsChange]) | |||||
| const { | const { | ||||
| defaultModel: rerankDefaultModel, | defaultModel: rerankDefaultModel, | ||||
| provider: configs.reranking_model?.reranking_provider_name, | provider: configs.reranking_model?.reranking_provider_name, | ||||
| model: configs.reranking_model?.reranking_model_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]) | }, [onMultipleRetrievalConfigChange, payload.retrieval_mode, rerankDefaultModel?.provider?.provider, rerankDefaultModel?.model, onRetrievalModeChange]) | ||||
| return ( | return ( | ||||
| <PortalToFollowElem | <PortalToFollowElem | ||||
| open={open} | |||||
| onOpenChange={setOpen} | |||||
| open={mergedOpen} | |||||
| onOpenChange={handleOpen} | |||||
| placement='bottom-end' | placement='bottom-end' | ||||
| offset={{ | offset={{ | ||||
| // mainAxis: 12, | |||||
| crossAxis: -2, | crossAxis: -2, | ||||
| }} | }} | ||||
| > | > | ||||
| onClick={() => { | onClick={() => { | ||||
| if (readonly) | if (readonly) | ||||
| return | 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> | </PortalToFollowElemTrigger> | ||||
| <PortalToFollowElemContent style={{ zIndex: 1001 }}> | <PortalToFollowElemContent style={{ zIndex: 1001 }}> | ||||
| <div className='w-[404px] pt-3 pb-4 px-4 shadow-xl rounded-2xl border border-gray-200 bg-white'> | <div className='w-[404px] pt-3 pb-4 px-4 shadow-xl rounded-2xl border border-gray-200 bg-white'> | ||||
| datasetConfigs={ | datasetConfigs={ | ||||
| { | { | ||||
| retrieval_model: payload.retrieval_mode, | 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, | 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, | score_threshold: multiple_retrieval_config?.score_threshold, | ||||
| datasets: { | datasets: { | ||||
| datasets: [], | datasets: [], | ||||
| }, | }, | ||||
| reranking_mode: multiple_retrieval_config?.reranking_mode, | |||||
| weights: multiple_retrieval_config?.weights, | |||||
| reranking_enable: multiple_retrieval_config?.reranking_enable, | |||||
| } | } | ||||
| } | } | ||||
| onChange={handleChange} | onChange={handleChange} | ||||
| singleRetrievalModelConfig={singleRetrievalModelConfig} | singleRetrievalModelConfig={singleRetrievalModelConfig} | ||||
| onSingleRetrievalModelChange={onSingleRetrievalModelChange} | onSingleRetrievalModelChange={onSingleRetrievalModelChange} | ||||
| onSingleRetrievalModelParamsChange={onSingleRetrievalModelParamsChange} | onSingleRetrievalModelParamsChange={onSingleRetrievalModelParamsChange} | ||||
| selectedDatasets={selectedDatasets} | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| </PortalToFollowElemContent> | </PortalToFollowElemContent> |
| import type { NodeDefault } from '../../types' | import type { NodeDefault } from '../../types' | ||||
| import type { KnowledgeRetrievalNodeType } from './types' | import type { KnowledgeRetrievalNodeType } from './types' | ||||
| import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants' | 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' | import { RETRIEVE_TYPE } from '@/types/app' | ||||
| const i18nPrefix = 'workflow' | const i18nPrefix = 'workflow' | ||||
| defaultValue: { | defaultValue: { | ||||
| query_variable_selector: [], | query_variable_selector: [], | ||||
| dataset_ids: [], | 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) { | getAvailablePrevNodes(isChatMode: boolean) { | ||||
| const nodes = isChatMode | const nodes = isChatMode |
| 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 | |||||
| } |
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import React from 'react' | |||||
| import { | |||||
| memo, | |||||
| useCallback, | |||||
| } from 'react' | |||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import VarReferencePicker from '../_base/components/variable/var-reference-picker' | import VarReferencePicker from '../_base/components/variable/var-reference-picker' | ||||
| import useConfig from './use-config' | import useConfig from './use-config' | ||||
| query, | query, | ||||
| setQuery, | setQuery, | ||||
| runResult, | runResult, | ||||
| rerankModelOpen, | |||||
| setRerankModelOpen, | |||||
| } = useConfig(id, data) | } = useConfig(id, data) | ||||
| const handleOpenFromPropsChange = useCallback((openFromProps: boolean) => { | |||||
| setRerankModelOpen(openFromProps) | |||||
| }, [setRerankModelOpen]) | |||||
| return ( | return ( | ||||
| <div className='mt-2'> | <div className='mt-2'> | ||||
| <div className='px-4 pb-4 space-y-4'> | <div className='px-4 pb-4 space-y-4'> | ||||
| singleRetrievalModelConfig={inputs.single_retrieval_config?.model} | singleRetrievalModelConfig={inputs.single_retrieval_config?.model} | ||||
| onSingleRetrievalModelChange={handleModelChanged as any} | onSingleRetrievalModelChange={handleModelChanged as any} | ||||
| onSingleRetrievalModelParamsChange={handleCompletionParamsChange} | 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 && (<div className='w-px h-3 bg-gray-200'></div>)} | ||||
| {!readOnly && ( | {!readOnly && ( | ||||
| ) | ) | ||||
| } | } | ||||
| export default React.memo(Panel) | |||||
| export default memo(Panel) |
| import type { CommonNodeType, ModelConfig, ValueSelector } from '@/app/components/workflow/types' | import type { CommonNodeType, ModelConfig, ValueSelector } from '@/app/components/workflow/types' | ||||
| import type { RETRIEVE_TYPE } from '@/types/app' | import type { RETRIEVE_TYPE } from '@/types/app' | ||||
| import type { | |||||
| RerankingModeEnum, | |||||
| WeightedScoreEnum, | |||||
| } from '@/models/datasets' | |||||
| export type MultipleRetrievalConfig = { | export type MultipleRetrievalConfig = { | ||||
| top_k: number | top_k: number | ||||
| provider: string | provider: string | ||||
| model: 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 = { | export type SingleRetrievalConfig = { |
| import { useCallback, useEffect, useRef, useState } from 'react' | |||||
| import { | |||||
| useCallback, | |||||
| useEffect, | |||||
| useRef, | |||||
| useState, | |||||
| } from 'react' | |||||
| import produce from 'immer' | import produce from 'immer' | ||||
| import { isEqual } from 'lodash-es' | import { isEqual } from 'lodash-es' | ||||
| import type { ValueSelector, Var } from '../../types' | import type { ValueSelector, Var } from '../../types' | ||||
| useWorkflow, | useWorkflow, | ||||
| } from '../../hooks' | } from '../../hooks' | ||||
| import type { KnowledgeRetrievalNodeType, MultipleRetrievalConfig } from './types' | import type { KnowledgeRetrievalNodeType, MultipleRetrievalConfig } from './types' | ||||
| import { | |||||
| getMultipleRetrievalConfig, | |||||
| getSelectedDatasetsMode, | |||||
| } from './utils' | |||||
| import { RETRIEVE_TYPE } from '@/types/app' | import { RETRIEVE_TYPE } from '@/types/app' | ||||
| import { DATASET_DEFAULT } from '@/config' | import { DATASET_DEFAULT } from '@/config' | ||||
| import type { DataSet } from '@/models/datasets' | import type { DataSet } from '@/models/datasets' | ||||
| draft.multiple_retrieval_config = { | draft.multiple_retrieval_config = { | ||||
| top_k: multipleRetrievalConfig?.top_k || DATASET_DEFAULT.top_k, | top_k: multipleRetrievalConfig?.top_k || DATASET_DEFAULT.top_k, | ||||
| score_threshold: multipleRetrievalConfig?.score_threshold, | 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) | setInputs(newInput) | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | // eslint-disable-next-line react-hooks/exhaustive-deps | ||||
| }, [currentProvider?.provider, currentModel, rerankDefaultModel]) | }, [currentProvider?.provider, currentModel, rerankDefaultModel]) | ||||
| const [selectedDatasets, setSelectedDatasets] = useState<DataSet[]>([]) | |||||
| const [rerankModelOpen, setRerankModelOpen] = useState(false) | |||||
| const handleRetrievalModeChange = useCallback((newMode: RETRIEVE_TYPE) => { | const handleRetrievalModeChange = useCallback((newMode: RETRIEVE_TYPE) => { | ||||
| const newInputs = produce(inputs, (draft) => { | const newInputs = produce(inputs, (draft) => { | ||||
| draft.retrieval_mode = newMode | draft.retrieval_mode = newMode | ||||
| if (newMode === RETRIEVE_TYPE.multiWay) { | 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 { | else { | ||||
| const hasSetModel = draft.single_retrieval_config?.model?.provider | const hasSetModel = draft.single_retrieval_config?.model?.provider | ||||
| } | } | ||||
| }) | }) | ||||
| setInputs(newInputs) | 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 handleMultipleRetrievalConfigChange = useCallback((newConfig: MultipleRetrievalConfig) => { | ||||
| const newInputs = produce(inputs, (draft) => { | const newInputs = produce(inputs, (draft) => { | ||||
| draft.multiple_retrieval_config = newConfig | |||||
| draft.multiple_retrieval_config = getMultipleRetrievalConfig(newConfig!, selectedDatasets) | |||||
| }) | }) | ||||
| setInputs(newInputs) | setInputs(newInputs) | ||||
| }, [inputs, setInputs]) | |||||
| }, [inputs, setInputs, selectedDatasets]) | |||||
| // datasets | // datasets | ||||
| const [selectedDatasets, setSelectedDatasets] = useState<DataSet[]>([]) | |||||
| useEffect(() => { | useEffect(() => { | ||||
| (async () => { | (async () => { | ||||
| const inputs = inputRef.current | const inputs = inputRef.current | ||||
| }, []) | }, []) | ||||
| const handleOnDatasetsChange = useCallback((newDatasets: DataSet[]) => { | const handleOnDatasetsChange = useCallback((newDatasets: DataSet[]) => { | ||||
| const { | |||||
| allEconomic, | |||||
| mixtureHighQualityAndEconomic, | |||||
| inconsistentEmbeddingModel, | |||||
| } = getSelectedDatasetsMode(newDatasets) | |||||
| const newInputs = produce(inputs, (draft) => { | const newInputs = produce(inputs, (draft) => { | ||||
| draft.dataset_ids = newDatasets.map(d => d.id) | 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) | setInputs(newInputs) | ||||
| setSelectedDatasets(newDatasets) | setSelectedDatasets(newDatasets) | ||||
| }, [inputs, setInputs]) | |||||
| if (allEconomic || mixtureHighQualityAndEconomic || inconsistentEmbeddingModel) | |||||
| setRerankModelOpen(true) | |||||
| }, [inputs, setInputs, payload.retrieval_mode]) | |||||
| const filterVar = useCallback((varPayload: Var) => { | const filterVar = useCallback((varPayload: Var) => { | ||||
| return varPayload.type === VarType.string | return varPayload.type === VarType.string | ||||
| query, | query, | ||||
| setQuery, | setQuery, | ||||
| runResult, | runResult, | ||||
| rerankModelOpen, | |||||
| setRerankModelOpen, | |||||
| } | } | ||||
| } | } | ||||
| 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 | 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 | |||||
| } |
| export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList' | export const NEED_REFRESH_APP_LIST_KEY = 'needRefreshAppList' | ||||
| export const DATASET_DEFAULT = { | export const DATASET_DEFAULT = { | ||||
| top_k: 2, | |||||
| score_threshold: 0.5, | |||||
| top_k: 4, | |||||
| score_threshold: 0.8, | |||||
| } | } | ||||
| export const APP_PAGE_LIMIT = 10 | export const APP_PAGE_LIMIT = 10 |
| isShowVisionConfig: boolean | isShowVisionConfig: boolean | ||||
| visionConfig: VisionSettings | visionConfig: VisionSettings | ||||
| setVisionConfig: (visionConfig: VisionSettings, noNotice?: boolean) => void | setVisionConfig: (visionConfig: VisionSettings, noNotice?: boolean) => void | ||||
| rerankSettingModalOpen: boolean | |||||
| setRerankSettingModalOpen: (rerankSettingModalOpen: boolean) => void | |||||
| } | } | ||||
| const DebugConfigurationContext = createContext<IDebugConfiguration>({ | const DebugConfigurationContext = createContext<IDebugConfiguration>({ | ||||
| showSelectDataSet: () => { }, | showSelectDataSet: () => { }, | ||||
| setDataSets: () => { }, | setDataSets: () => { }, | ||||
| datasetConfigs: { | datasetConfigs: { | ||||
| retrieval_model: RETRIEVE_TYPE.oneWay, | |||||
| retrieval_model: RETRIEVE_TYPE.multiWay, | |||||
| reranking_model: { | reranking_model: { | ||||
| reranking_provider_name: '', | reranking_provider_name: '', | ||||
| reranking_model_name: '', | reranking_model_name: '', | ||||
| transfer_methods: [TransferMethod.remote_url], | transfer_methods: [TransferMethod.remote_url], | ||||
| }, | }, | ||||
| setVisionConfig: () => { }, | setVisionConfig: () => { }, | ||||
| rerankSettingModalOpen: false, | |||||
| setRerankSettingModalOpen: () => { }, | |||||
| }) | }) | ||||
| export const useDebugConfigurationContext = () => useContext(DebugConfigurationContext) | export const useDebugConfigurationContext = () => useContext(DebugConfigurationContext) |
| 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, | |||||
| } | |||||
| } |
| }, | }, | ||||
| docsFailedNotice: 'documents failed to be indexed', | docsFailedNotice: 'documents failed to be indexed', | ||||
| retry: 'Retry', | 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 | export default translation |
| }, | }, | ||||
| docsFailedNotice: '文档无法被索引', | docsFailedNotice: '文档无法被索引', | ||||
| retry: '重试', | 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 | export default translation |
| data: IndexingStatusResponse[] | data: IndexingStatusResponse[] | ||||
| total: number | 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, | |||||
| }, | |||||
| } |
| import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem, TtsAutoPlay } from '@/types/app' | 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 type Inputs = Record<string, string | number | object> | ||||
| export enum PromptMode { | export enum PromptMode { | ||||
| } | } | ||||
| top_k: number | top_k: number | ||||
| score_threshold_enabled: boolean | score_threshold_enabled: boolean | ||||
| score_threshold?: number | null | |||||
| score_threshold: number | null | undefined | |||||
| datasets: { | datasets: { | ||||
| datasets: { | datasets: { | ||||
| enabled: boolean | enabled: boolean | ||||
| id: string | 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 = { | export type DebugRequestBody = { |
| import type { CollectionType } from '@/app/components/tools/types' | import type { CollectionType } from '@/app/components/tools/types' | ||||
| import type { LanguagesSupported } from '@/i18n/language' | import type { LanguagesSupported } from '@/i18n/language' | ||||
| import type { Tag } from '@/app/components/base/tag-management/constant' | import type { Tag } from '@/app/components/base/tag-management/constant' | ||||
| import type { | |||||
| RerankingModeEnum, | |||||
| WeightedScoreEnum, | |||||
| } from '@/models/datasets' | |||||
| export enum Theme { | export enum Theme { | ||||
| light = 'light', | light = 'light', | ||||
| top_k: number | top_k: number | ||||
| score_threshold_enabled: boolean | score_threshold_enabled: boolean | ||||
| score_threshold: number | 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 | |||||
| } | |||||
| } | |||||
| } | } |