| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { | import { | ||||
| RiAlertFill, | |||||
| RiQuestionLine, | RiQuestionLine, | ||||
| } from '@remixicon/react' | } from '@remixicon/react' | ||||
| import WeightedScore from './weighted-score' | import WeightedScore from './weighted-score' | ||||
| 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 { | import type { | ||||
| DataSet, | DataSet, | ||||
| WeightedScoreEnum, | |||||
| } from '@/models/datasets' | } from '@/models/datasets' | ||||
| import { RerankingModeEnum } from '@/models/datasets' | import { RerankingModeEnum } from '@/models/datasets' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| }) | }) | ||||
| } | } | ||||
| const handleWeightedScoreChange = (value: { type: WeightedScoreEnum; value: number[] }) => { | |||||
| const handleWeightedScoreChange = (value: { value: number[] }) => { | |||||
| const configs = { | const configs = { | ||||
| ...datasetConfigs, | ...datasetConfigs, | ||||
| weights: { | weights: { | ||||
| ...datasetConfigs.weights!, | ...datasetConfigs.weights!, | ||||
| weight_type: value.type, | |||||
| vector_setting: { | vector_setting: { | ||||
| ...datasetConfigs.weights!.vector_setting!, | ...datasetConfigs.weights!.vector_setting!, | ||||
| vector_weight: value.value[0], | vector_weight: value.value[0], | ||||
| popupContent={( | popupContent={( | ||||
| <div className='w-[320px]'> | <div className='w-[320px]'> | ||||
| {t('dataset.nTo1RetrievalLegacy')} | {t('dataset.nTo1RetrievalLegacy')} | ||||
| <a | |||||
| className='underline' | |||||
| href={LEGACY_LINK_MAP[language]} | |||||
| target='_blank' | |||||
| rel='noopener noreferrer' | |||||
| > | |||||
| ({t('dataset.nTo1RetrievalLegacyLink')}) | |||||
| </a> | |||||
| </div> | </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) }} | ||||
| extra={( | |||||
| <div className='flex pl-3 pr-1 py-3 border-t border-divider-subtle bg-state-warning-hover rounded-b-xl'> | |||||
| <RiAlertFill className='shrink-0 mr-1.5 w-4 h-4 text-text-warning-secondary' /> | |||||
| <div className='system-xs-medium text-text-primary'> | |||||
| {t('dataset.nTo1RetrievalLegacyLinkText')} | |||||
| <a | |||||
| className='text-text-accent' | |||||
| href={LEGACY_LINK_MAP[language]} | |||||
| target='_blank' | |||||
| rel='noopener noreferrer' | |||||
| > | |||||
| {t('dataset.nTo1RetrievalLegacyLink')} | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| )} | |||||
| /> | /> | ||||
| <RadioCard | <RadioCard | ||||
| icon={<MultiPathRetrieval className='shrink-0 mr-3 w-9 h-9 rounded-lg' />} | icon={<MultiPathRetrieval className='shrink-0 mr-3 w-9 h-9 rounded-lg' />} | ||||
| <div className='mt-2 space-y-4'> | <div className='mt-2 space-y-4'> | ||||
| <WeightedScore | <WeightedScore | ||||
| value={{ | value={{ | ||||
| type: datasetConfigs.weights!.weight_type, | |||||
| value: [ | value: [ | ||||
| datasetConfigs.weights!.vector_setting.vector_weight, | datasetConfigs.weights!.vector_setting.vector_weight, | ||||
| datasetConfigs.weights!.keyword_setting.keyword_weight, | datasetConfigs.weights!.keyword_setting.keyword_weight, |
| .weightedScoreSliderTrack { | |||||
| background: var(--color-util-colors-blue-light-blue-light-500) !important; | |||||
| } | |||||
| .weightedScoreSliderTrack-1 { | |||||
| background: transparent !important; | |||||
| } |
| import { memo, useCallback } from 'react' | |||||
| import { memo } from 'react' | |||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import { | |||||
| DEFAULT_WEIGHTED_SCORE, | |||||
| WeightedScoreEnum, | |||||
| } from '@/models/datasets' | |||||
| import './weighted-score.css' | |||||
| import Slider from '@/app/components/base/slider' | import Slider from '@/app/components/base/slider' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| } | } | ||||
| type Value = { | type Value = { | ||||
| type: WeightedScoreEnum | |||||
| value: number[] | value: number[] | ||||
| } | } | ||||
| onChange = () => {}, | onChange = () => {}, | ||||
| }: WeightedScoreProps) => { | }: WeightedScoreProps) => { | ||||
| const { t } = useTranslation() | 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 ( | return ( | ||||
| <div> | <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> | |||||
| <div className='px-3 pt-5 h-[52px] space-x-3 rounded-lg border border-components-panel-border'> | |||||
| <Slider | <Slider | ||||
| className={cn('grow h-0.5 bg-gradient-to-r from-[#53B1FD] to-[#2ED3B7]', disabled && 'cursor-not-allowed')} | |||||
| className={cn('grow h-0.5 !bg-util-colors-teal-teal-500 rounded-full')} | |||||
| max={1.0} | max={1.0} | ||||
| min={0} | min={0} | ||||
| step={0.1} | step={0.1} | ||||
| value={value.value[0]} | 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' | |||||
| onChange={v => onChange({ value: [v, (10 - v * 10) / 10] })} | |||||
| trackClassName='weightedScoreSliderTrack' | |||||
| /> | /> | ||||
| <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 className='flex justify-between mt-1'> | |||||
| <div className='shrink-0 flex items-center w-[90px] system-xs-semibold-uppercase text-util-colors-blue-light-blue-light-500'> | |||||
| <div className='mr-1 truncate uppercase' title={t('dataset.weightedScore.semantic') || ''}> | |||||
| {t('dataset.weightedScore.semantic')} | |||||
| </div> | |||||
| {formatNumber(value.value[0])} | |||||
| </div> | |||||
| <div className='shrink-0 flex items-center justify-end w-[90px] system-xs-semibold-uppercase text-util-colors-teal-teal-500'> | |||||
| {formatNumber(value.value[1])} | |||||
| <div className='ml-1 truncate uppercase' title={t('dataset.weightedScore.keyword') || ''}> | |||||
| {t('dataset.weightedScore.keyword')} | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> |
| onChosen: () => void | onChosen: () => void | ||||
| chosenConfig?: React.ReactNode | chosenConfig?: React.ReactNode | ||||
| icon?: JSX.Element | icon?: JSX.Element | ||||
| extra?: React.ReactNode | |||||
| } | } | ||||
| const RadioCard: FC<Props> = ({ | const RadioCard: FC<Props> = ({ | ||||
| isChosen, | isChosen, | ||||
| onChosen, | onChosen, | ||||
| icon, | icon, | ||||
| extra, | |||||
| }) => { | }) => { | ||||
| return ( | return ( | ||||
| <div | <div | ||||
| className={cn(s.item, isChosen && s.active, 'flex')} | |||||
| className={cn(s.item, isChosen && s.active)} | |||||
| onClick={onChosen} | onClick={onChosen} | ||||
| > | > | ||||
| {icon} | |||||
| <div> | |||||
| <div className='flex justify-between items-center'> | |||||
| <div className='leading-5 text-sm font-medium text-gray-900'>{title}</div> | |||||
| <div className={s.radio}></div> | |||||
| <div className='flex px-3 py-2'> | |||||
| {icon} | |||||
| <div> | |||||
| <div className='flex justify-between items-center'> | |||||
| <div className='leading-5 text-sm font-medium text-gray-900'>{title}</div> | |||||
| <div className={s.radio}></div> | |||||
| </div> | |||||
| <div className='leading-[18px] text-xs font-normal text-gray-500'>{description}</div> | |||||
| </div> | </div> | ||||
| <div className='leading-[18px] text-xs font-normal text-gray-500'>{description}</div> | |||||
| </div> | </div> | ||||
| {extra} | |||||
| </div> | </div> | ||||
| ) | ) | ||||
| } | } |
| .item { | .item { | ||||
| @apply relative p-4 rounded-xl border border-gray-100 cursor-pointer; | |||||
| @apply relative rounded-xl border border-gray-100 cursor-pointer; | |||||
| background-color: #fcfcfd; | background-color: #fcfcfd; | ||||
| } | } | ||||
| import type { RETRIEVE_TYPE } from '@/types/app' | import type { RETRIEVE_TYPE } from '@/types/app' | ||||
| import type { | import type { | ||||
| RerankingModeEnum, | RerankingModeEnum, | ||||
| WeightedScoreEnum, | |||||
| } from '@/models/datasets' | } from '@/models/datasets' | ||||
| export type MultipleRetrievalConfig = { | export type MultipleRetrievalConfig = { | ||||
| } | } | ||||
| reranking_mode?: RerankingModeEnum | reranking_mode?: RerankingModeEnum | ||||
| weights?: { | weights?: { | ||||
| weight_type: WeightedScoreEnum | |||||
| vector_setting: { | vector_setting: { | ||||
| vector_weight: number | vector_weight: number | ||||
| embedding_provider_name: string | embedding_provider_name: string |
| handleMultipleRetrievalConfigChange, | handleMultipleRetrievalConfigChange, | ||||
| handleModelChanged, | handleModelChanged, | ||||
| handleCompletionParamsChange, | handleCompletionParamsChange, | ||||
| selectedDatasets, | |||||
| selectedDatasets: selectedDatasets.filter(d => d.name), | |||||
| handleOnDatasetsChange, | handleOnDatasetsChange, | ||||
| isShowSingleRun, | isShowSingleRun, | ||||
| hideSingleRun, | hideSingleRun, |
| import { | import { | ||||
| DEFAULT_WEIGHTED_SCORE, | DEFAULT_WEIGHTED_SCORE, | ||||
| RerankingModeEnum, | RerankingModeEnum, | ||||
| WeightedScoreEnum, | |||||
| } from '@/models/datasets' | } from '@/models/datasets' | ||||
| import { RETRIEVE_METHOD } from '@/types/app' | import { RETRIEVE_METHOD } from '@/types/app' | ||||
| import { DATASET_DEFAULT } from '@/config' | import { DATASET_DEFAULT } from '@/config' | ||||
| if (allHighQuality && !inconsistentEmbeddingModel && (reranking_mode === RerankingModeEnum.WeightedScore || reranking_mode === undefined) && !weights) { | if (allHighQuality && !inconsistentEmbeddingModel && (reranking_mode === RerankingModeEnum.WeightedScore || reranking_mode === undefined) && !weights) { | ||||
| result.weights = { | result.weights = { | ||||
| weight_type: WeightedScoreEnum.Customized, | |||||
| vector_setting: { | vector_setting: { | ||||
| vector_weight: allHighQualityVectorSearch | vector_weight: allHighQualityVectorSearch | ||||
| ? DEFAULT_WEIGHTED_SCORE.allHighQualityVectorSearch.semantic | ? DEFAULT_WEIGHTED_SCORE.allHighQualityVectorSearch.semantic |
| }, | }, | ||||
| nTo1RetrievalLegacy: 'N-to-1 retrieval will be officially deprecated from September. It is recommended to use the latest Multi-path retrieval to obtain better results. ', | nTo1RetrievalLegacy: 'N-to-1 retrieval will be officially deprecated from September. It is recommended to use the latest Multi-path retrieval to obtain better results. ', | ||||
| nTo1RetrievalLegacyLink: 'Learn more', | nTo1RetrievalLegacyLink: 'Learn more', | ||||
| nTo1RetrievalLegacyLinkText: ' N-to-1 retrieval will be officially deprecated in September.', | |||||
| } | } | ||||
| export default translation | export default translation |
| }, | }, | ||||
| nTo1RetrievalLegacy: '9 月 1 日起我们将不再提供此能力,推荐使用最新的多路召回获得更好的检索效果。', | nTo1RetrievalLegacy: '9 月 1 日起我们将不再提供此能力,推荐使用最新的多路召回获得更好的检索效果。', | ||||
| nTo1RetrievalLegacyLink: '了解更多', | nTo1RetrievalLegacyLink: '了解更多', | ||||
| nTo1RetrievalLegacyLinkText: '9 月 1 日起我们将不再提供此能力。', | |||||
| } | } | ||||
| export default translation | export default translation |
| import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem, TtsAutoPlay } from '@/types/app' | import type { AgentStrategy, ModelModeType, RETRIEVE_TYPE, ToolItem, TtsAutoPlay } from '@/types/app' | ||||
| import type { | import type { | ||||
| RerankingModeEnum, | RerankingModeEnum, | ||||
| WeightedScoreEnum, | |||||
| } from '@/models/datasets' | } from '@/models/datasets' | ||||
| export type Inputs = Record<string, string | number | object> | export type Inputs = Record<string, string | number | object> | ||||
| } | } | ||||
| reranking_mode?: RerankingModeEnum | reranking_mode?: RerankingModeEnum | ||||
| weights?: { | weights?: { | ||||
| weight_type: WeightedScoreEnum | |||||
| vector_setting: { | vector_setting: { | ||||
| vector_weight: number | vector_weight: number | ||||
| embedding_provider_name: string | embedding_provider_name: string |