From 4d68aadc1c5f83f04b918e84e51d3f66eeab29fc Mon Sep 17 00:00:00 2001 From: twwu Date: Tue, 13 May 2025 15:35:21 +0800 Subject: [PATCH] Refactor: Replace IndexMethodRadio with IndexMethod component, add keyword number functionality, and update related translations --- .../dataset-config/settings-modal/index.tsx | 7 +- web/app/components/base/slider/index.tsx | 2 +- .../datasets/create/website/base/input.tsx | 2 +- .../settings/chunk-structure/hooks.tsx | 8 +- .../settings/chunk-structure/index.tsx | 3 +- .../settings/chunk-structure/types.ts | 1 + .../datasets/settings/form/index.tsx | 169 +++++++++--------- .../index-method-radio/assets/economy.svg | 5 - .../assets/high-quality.svg | 12 -- .../settings/index-method-radio/index.tsx | 106 ----------- .../datasets/settings/index-method/index.tsx | 94 ++++++++++ .../settings/index-method/keyword-number.tsx | 52 ++++++ .../datasets/settings/option-card.tsx | 41 ++++- web/i18n/en-US/dataset-settings.ts | 3 +- web/i18n/zh-Hans/dataset-settings.ts | 3 +- web/models/datasets.ts | 1 + 16 files changed, 286 insertions(+), 223 deletions(-) delete mode 100644 web/app/components/datasets/settings/index-method-radio/assets/economy.svg delete mode 100644 web/app/components/datasets/settings/index-method-radio/assets/high-quality.svg delete mode 100644 web/app/components/datasets/settings/index-method-radio/index.tsx create mode 100644 web/app/components/datasets/settings/index-method/index.tsx create mode 100644 web/app/components/datasets/settings/index-method/keyword-number.tsx diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index 3170d33a82..cc2fb061b8 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -6,7 +6,7 @@ import { isEqual } from 'lodash-es' import { RiCloseLine } from '@remixicon/react' import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development' import cn from '@/utils/classnames' -import IndexMethodRadio from '@/app/components/datasets/settings/index-method-radio' +import IndexMethod from '@/app/components/datasets/settings/index-method' import Divider from '@/app/components/base/divider' import Button from '@/app/components/base/button' import Input from '@/app/components/base/input' @@ -245,11 +245,10 @@ const SettingsModal: FC = ({
{t('datasetSettings.form.indexMethod')}
- setIndexMethod(v!)} - docForm={currentDataset.doc_form} currentValue={currentDataset.indexing_technique} />
diff --git a/web/app/components/base/slider/index.tsx b/web/app/components/base/slider/index.tsx index 2cfbf80363..1b41ee64c1 100644 --- a/web/app/components/base/slider/index.tsx +++ b/web/app/components/base/slider/index.tsx @@ -27,7 +27,7 @@ const Slider: React.FC = ({ }) => { return = ({ const value = e.target.value if (isNumber) { let numberValue = Number.parseInt(value, 10) // integer only - if (isNaN(numberValue)) { + if (Number.isNaN(numberValue)) { onChange('') return } diff --git a/web/app/components/datasets/settings/chunk-structure/hooks.tsx b/web/app/components/datasets/settings/chunk-structure/hooks.tsx index 84bae7b58d..6ac75caee3 100644 --- a/web/app/components/datasets/settings/chunk-structure/hooks.tsx +++ b/web/app/components/datasets/settings/chunk-structure/hooks.tsx @@ -9,7 +9,8 @@ import { ChunkingMode } from '@/models/datasets' export const useChunkStructure = () => { const GeneralOption: Option = { id: ChunkingMode.text, - icon: , + icon: , + iconActiveColor: 'text-util-colors-indigo-indigo-600', title: 'General', description: 'General text chunking mode, the chunks retrieved and recalled are the same.', effectColor: EffectColor.indigo, @@ -17,7 +18,8 @@ export const useChunkStructure = () => { } const ParentChildOption: Option = { id: ChunkingMode.parentChild, - icon: , + icon: , + iconActiveColor: 'text-util-colors-blue-light-blue-light-500', title: 'Parent-Child', description: 'When using the parent-child mode, the child-chunk is used for retrieval and the parent-chunk is used for recall as context.', effectColor: EffectColor.blueLight, @@ -25,7 +27,7 @@ export const useChunkStructure = () => { } const QuestionAnswerOption: Option = { id: ChunkingMode.qa, - icon: , + icon: , title: 'Q&A', description: 'When using structured Q&A data, you can create documents that pair questions with answers. These documents are indexed based on the question portion, allowing the system to retrieve relevant answers based on query similarity', } diff --git a/web/app/components/datasets/settings/chunk-structure/index.tsx b/web/app/components/datasets/settings/chunk-structure/index.tsx index 3a65212dbc..4919bf9cd1 100644 --- a/web/app/components/datasets/settings/chunk-structure/index.tsx +++ b/web/app/components/datasets/settings/chunk-structure/index.tsx @@ -24,12 +24,13 @@ const ChunkStructure = ({ key={option.id} id={option.id} icon={option.icon} + iconActiveColor={option.iconActiveColor} title={option.title} description={option.description} onClick={() => { onChunkStructureChange(option.id) }} - showHighlightBorder={chunkStructure === option.id} + isActive={chunkStructure === option.id} effectColor={option.effectColor} showEffectColor className='gap-x-1.5 p-3 pr-4' diff --git a/web/app/components/datasets/settings/chunk-structure/types.ts b/web/app/components/datasets/settings/chunk-structure/types.ts index 64c21d65f2..f92b24de47 100644 --- a/web/app/components/datasets/settings/chunk-structure/types.ts +++ b/web/app/components/datasets/settings/chunk-structure/types.ts @@ -10,6 +10,7 @@ export enum EffectColor { export type Option = { id: ChunkingMode icon?: React.ReactNode + iconActiveColor?: string title: string description?: string effectColor?: EffectColor diff --git a/web/app/components/datasets/settings/form/index.tsx b/web/app/components/datasets/settings/form/index.tsx index a072c265b1..60d09c792b 100644 --- a/web/app/components/datasets/settings/form/index.tsx +++ b/web/app/components/datasets/settings/form/index.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next' import { useSWRConfig } from 'swr' import { unstable_serialize } from 'swr/infinite' import PermissionSelector from '../permission-selector' -import IndexMethodRadio from '../index-method-radio' +import IndexMethod from '../index-method' import RetrievalSettings from '../../external-knowledge-base/create/RetrievalSettings' import { IndexingType } from '../../create/step-two' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' @@ -17,7 +17,7 @@ import Textarea from '@/app/components/base/textarea' import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development' import { updateDatasetSetting } from '@/service/datasets' import type { IconInfo } from '@/models/datasets' -import { type DataSetListResponse, DatasetPermission } from '@/models/datasets' +import { ChunkingMode, type DataSetListResponse, DatasetPermission } from '@/models/datasets' import DatasetDetailContext from '@/context/dataset-detail' import type { AppIconType, RetrievalConfig } from '@/types/app' import { useSelector as useAppContextWithSelector } from '@/context/app-context' @@ -39,7 +39,7 @@ import ChunkStructure from '../chunk-structure' import Toast from '@/app/components/base/toast' import { RiAlertFill } from '@remixicon/react' -const rowClass = 'flex' +const rowClass = 'flex gap-x-1' const labelClass = 'flex items-center shrink-0 w-[180px] h-7 pt-1' const getKey = (pageIndex: number, previousPageData: DataSetListResponse) => { @@ -66,13 +66,14 @@ const Form = () => { const [showAppIconPicker, setShowAppIconPicker] = useState(false) const [description, setDescription] = useState(currentDataset?.description ?? '') const [permission, setPermission] = useState(currentDataset?.permission) - const [chunkStructure, setChunkStructure] = useState(currentDataset?.doc_form) + const [chunkStructure, setChunkStructure] = useState(currentDataset?.doc_form ?? ChunkingMode.text) const [topK, setTopK] = useState(currentDataset?.external_retrieval_model.top_k ?? 2) const [scoreThreshold, setScoreThreshold] = useState(currentDataset?.external_retrieval_model.score_threshold ?? 0.5) const [scoreThresholdEnabled, setScoreThresholdEnabled] = useState(currentDataset?.external_retrieval_model.score_threshold_enabled ?? false) const [selectedMemberIDs, setSelectedMemberIDs] = useState(currentDataset?.partial_member_list || []) const [memberList, setMemberList] = useState([]) - const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique) + const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique ?? IndexingType.QUALIFIED) + const [keywordNumber, setKeywordNumber] = useState(currentDataset?.keyword_number ?? 10) const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig) const [embeddingModel, setEmbeddingModel] = useState( currentDataset?.embedding_model @@ -205,6 +206,8 @@ const Form = () => { } } + const isShowIndexMethod = chunkStructure !== ChunkingMode.parentChild && currentDataset && currentDataset.indexing_technique + return (
{/* Dataset name and icon */} @@ -261,88 +264,92 @@ const Form = () => { />
- - {/* Chunk Structure */} -
-
-
-
- {t('datasetSettings.form.chunkStructure.title')} -
-
- - {t('datasetSettings.form.chunkStructure.learnMore')} - - {t('datasetSettings.form.chunkStructure.description')} -
-
-
-
- -
-
- {currentDataset && currentDataset.indexing_technique && ( - <> - -
-
-
{t('datasetSettings.form.indexMethod')}
-
-
- setIndexMethod(v!)} - docForm={currentDataset.doc_form} - currentValue={currentDataset.indexing_technique} - /> - {currentDataset.indexing_technique === IndexingType.ECONOMICAL && indexMethod === IndexingType.QUALIFIED && ( -
-
-
- + { + !currentDataset?.doc_form && ( + <> + + {/* Chunk Structure */} +
+
+
+
+ {t('datasetSettings.form.chunkStructure.title')} +
+
+ + {t('datasetSettings.form.chunkStructure.learnMore')} + + {t('datasetSettings.form.chunkStructure.description')}
- - {t('datasetSettings.form.upgradeHighQualityTip')} -
- )} +
+
+ +
-
- + + ) + } + {(isShowIndexMethod || indexMethod === 'high_quality') && ( + )} - {indexMethod === 'high_quality' && ( - <> -
-
-
{t('datasetSettings.form.embeddingModel')}
-
-
- { - setEmbeddingModel(model) - }} - /> + {isShowIndexMethod && ( +
+
+
{t('datasetSettings.form.indexMethod')}
+
+
+ setIndexMethod(v!)} + currentValue={currentDataset.indexing_technique} + keywordNumber={keywordNumber} + onKeywordNumberChange={setKeywordNumber} + /> + {currentDataset.indexing_technique === IndexingType.ECONOMICAL && indexMethod === IndexingType.QUALIFIED && ( +
+
+
+ +
+ + {t('datasetSettings.form.upgradeHighQualityTip')} + +
+ )} +
+
+ )} + {indexMethod === IndexingType.QUALIFIED && ( +
+
+
+ {t('datasetSettings.form.embeddingModel')}
- +
+ +
+
)} {/* Retrieval Method Config */} {currentDataset?.provider === 'external' diff --git a/web/app/components/datasets/settings/index-method-radio/assets/economy.svg b/web/app/components/datasets/settings/index-method-radio/assets/economy.svg deleted file mode 100644 index 6b135bf197..0000000000 --- a/web/app/components/datasets/settings/index-method-radio/assets/economy.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/web/app/components/datasets/settings/index-method-radio/assets/high-quality.svg b/web/app/components/datasets/settings/index-method-radio/assets/high-quality.svg deleted file mode 100644 index dcb9aa2447..0000000000 --- a/web/app/components/datasets/settings/index-method-radio/assets/high-quality.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/web/app/components/datasets/settings/index-method-radio/index.tsx b/web/app/components/datasets/settings/index-method-radio/index.tsx deleted file mode 100644 index f0d2b327a4..0000000000 --- a/web/app/components/datasets/settings/index-method-radio/index.tsx +++ /dev/null @@ -1,106 +0,0 @@ -'use client' -import { useTranslation } from 'react-i18next' -import Image from 'next/image' -import { useRef } from 'react' -import { useHover } from 'ahooks' -import { IndexingType } from '../../create/step-two' -import { OptionCard } from '../../create/step-two/option-card' -import { indexMethodIcon } from '../../create/icons' -import classNames from '@/utils/classnames' -import type { DataSet } from '@/models/datasets' -import { ChunkingMode } from '@/models/datasets' -import Badge from '@/app/components/base/badge' -import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem' - -type IIndexMethodRadioProps = { - value?: DataSet['indexing_technique'] - onChange: (v?: DataSet['indexing_technique']) => void - disable?: boolean - docForm?: ChunkingMode - currentValue?: DataSet['indexing_technique'] -} - -const IndexMethodRadio = ({ - value, - onChange, - disable, - docForm, - currentValue, -}: IIndexMethodRadioProps) => { - const { t } = useTranslation() - const economyDomRef = useRef(null) - const isHoveringEconomy = useHover(economyDomRef) - const isEconomyDisabled = currentValue === IndexingType.QUALIFIED - const options = [ - { - key: 'high_quality', - text:
- {t('datasetCreation.stepTwo.qualified')} - - {t('datasetCreation.stepTwo.recommend')} - -
, - desc: t('datasetSettings.form.indexMethodHighQualityTip'), - }, - { - key: 'economy', - text: t('datasetSettings.form.indexMethodEconomy'), - desc: t('datasetSettings.form.indexMethodEconomyTip'), - }, - ] - - return ( -
- { - options.map((option) => { - const isParentChild = docForm === ChunkingMode.parentChild - return ( - - - { - if (isParentChild && option.key === IndexingType.ECONOMICAL) - return - if (isEconomyDisabled && option.key === IndexingType.ECONOMICAL) - return - if (!disable) - onChange(option.key as DataSet['indexing_technique']) - } } - icon={ - {option.desc} - } - title={option.text} - description={option.desc} - ref={option.key === 'economy' ? economyDomRef : undefined} - className={classNames((isEconomyDisabled && option.key === 'economy') && 'cursor-not-allowed')} - > - - - -
- {t('datasetSettings.form.indexMethodChangeToEconomyDisabledTip')} -
-
-
- ) - }) - } -
- ) -} - -export default IndexMethodRadio diff --git a/web/app/components/datasets/settings/index-method/index.tsx b/web/app/components/datasets/settings/index-method/index.tsx new file mode 100644 index 0000000000..6490c68658 --- /dev/null +++ b/web/app/components/datasets/settings/index-method/index.tsx @@ -0,0 +1,94 @@ +'use client' +import { useTranslation } from 'react-i18next' +import { useRef } from 'react' +import { useHover } from 'ahooks' +import { IndexingType } from '../../create/step-two' +import classNames from '@/utils/classnames' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import { Economic, HighQuality } from '@/app/components/base/icons/src/vender/knowledge' +import { EffectColor } from '../chunk-structure/types' +import OptionCard from '../option-card' +import KeywordNumber from './keyword-number' + +type IndexMethodProps = { + value: IndexingType + onChange: (id: IndexingType) => void + disabled?: boolean + currentValue?: IndexingType + keywordNumber: number + onKeywordNumberChange: (value: number) => void +} + +const IndexMethod = ({ + value, + onChange, + disabled, + currentValue, + keywordNumber, + onKeywordNumberChange, +}: IndexMethodProps) => { + const { t } = useTranslation() + const economyDomRef = useRef(null) + const isHoveringEconomy = useHover(economyDomRef) + const isEconomyDisabled = currentValue === IndexingType.QUALIFIED + + return ( +
+ {/* High Quality */} + } + iconActiveColor='text-util-colors-orange-orange-500' + title={t('datasetCreation.stepTwo.qualified')} + description={t('datasetSettings.form.indexMethodHighQualityTip')} + disabled={disabled} + isRecommended + effectColor={EffectColor.orange} + showEffectColor + className='gap-x-2' + /> + {/* Economy */} + + + } + iconActiveColor='text-util-colors-indigo-indigo-600' + title={t('datasetSettings.form.indexMethodEconomy')} + description={t('datasetSettings.form.indexMethodEconomyTip', { count: keywordNumber })} + disabled={disabled || isEconomyDisabled} + effectColor={EffectColor.indigo} + showEffectColor + showChildren + className='gap-x-2' + > + + + + +
+ {t('datasetSettings.form.indexMethodChangeToEconomyDisabledTip')} +
+
+
+
+ ) +} + +export default IndexMethod diff --git a/web/app/components/datasets/settings/index-method/keyword-number.tsx b/web/app/components/datasets/settings/index-method/keyword-number.tsx new file mode 100644 index 0000000000..24878f305c --- /dev/null +++ b/web/app/components/datasets/settings/index-method/keyword-number.tsx @@ -0,0 +1,52 @@ +import { InputNumber } from '@/app/components/base/input-number' +import Slider from '@/app/components/base/slider' +import Tooltip from '@/app/components/base/tooltip' +import { RiQuestionLine } from '@remixicon/react' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' + +type KeyWordNumberProps = { + keywordNumber: number + onKeywordNumberChange: (value: number) => void +} + +const KeyWordNumber = ({ + keywordNumber, + onKeywordNumberChange, +}: KeyWordNumberProps) => { + const { t } = useTranslation() + + const handleInputChange = useCallback((value: number | undefined) => { + if (value) + onKeywordNumberChange(value) + }, [onKeywordNumberChange]) + + return ( +
+
+
+ {t('datasetSettings.form.numberOfKeywords')} +
+ + + +
+ + +
+ ) +} + +export default React.memo(KeyWordNumber) diff --git a/web/app/components/datasets/settings/option-card.tsx b/web/app/components/datasets/settings/option-card.tsx index 88335abd74..15e69db2bc 100644 --- a/web/app/components/datasets/settings/option-card.tsx +++ b/web/app/components/datasets/settings/option-card.tsx @@ -4,6 +4,7 @@ import cn from '@/utils/classnames' import Badge from '@/app/components/base/badge' import { useTranslation } from 'react-i18next' import { EffectColor } from './chunk-structure/types' +import { ArrowShape } from '../../base/icons/src/vender/knowledge' const HEADER_EFFECT_MAP: Record = { [EffectColor.indigo]: 'bg-util-colors-indigo-indigo-600 opacity-50', @@ -14,36 +15,51 @@ const HEADER_EFFECT_MAP: Record = { type OptionCardProps = { id: T className?: string - showHighlightBorder?: boolean + isActive?: boolean icon?: ReactNode + iconActiveColor?: string title: string description?: string isRecommended?: boolean effectColor?: EffectColor showEffectColor?: boolean + disabled?: boolean onClick?: (id: T) => void + children?: ReactNode + showChildren?: boolean + ref?: React.Ref } const OptionCard = ({ id, className, - showHighlightBorder, + isActive, icon, + iconActiveColor, title, description, isRecommended, effectColor, showEffectColor, + disabled, onClick, + children, + showChildren, + ref, }: OptionCardProps) => { const { t } = useTranslation() return (
onClick?.(id)} + onClick={() => { + if (disabled) return + onClick?.(id) + }} >
({ } { icon && ( -
+
{icon}
) }
- + {title} { @@ -86,8 +105,16 @@ const OptionCard = ({ }
+ { + children && showChildren && ( +
+ + {children} +
+ ) + }
) } -export default React.memo(OptionCard) +export default React.memo(OptionCard) as typeof OptionCard diff --git a/web/i18n/en-US/dataset-settings.ts b/web/i18n/en-US/dataset-settings.ts index f6b50b9b3b..61fde8aa0d 100644 --- a/web/i18n/en-US/dataset-settings.ts +++ b/web/i18n/en-US/dataset-settings.ts @@ -27,7 +27,8 @@ const translation = { indexMethodHighQualityTip: 'Calling the embedding model to process documents for more precise retrieval helps LLM generate high-quality answers.', upgradeHighQualityTip: 'Once upgrading to High Quality mode, reverting to Economical mode is not available', indexMethodEconomy: 'Economical', - indexMethodEconomyTip: 'Using 10 keywords per chunk for retrieval, no tokens are consumed at the expense of reduced retrieval accuracy.', + indexMethodEconomyTip: 'Using {{count}} keywords per chunk for retrieval, no tokens are consumed at the expense of reduced retrieval accuracy.', + numberOfKeywords: 'Number of Keywords', embeddingModel: 'Embedding Model', embeddingModelTip: 'Change the embedded model, please go to ', embeddingModelTipLink: 'Settings', diff --git a/web/i18n/zh-Hans/dataset-settings.ts b/web/i18n/zh-Hans/dataset-settings.ts index f3d5ff6dc0..c5bb54f7ed 100644 --- a/web/i18n/zh-Hans/dataset-settings.ts +++ b/web/i18n/zh-Hans/dataset-settings.ts @@ -27,7 +27,8 @@ const translation = { indexMethodHighQualityTip: '调用嵌入模型来处理文档以实现更精确的检索,可以帮助大语言模型生成高质量的回答。', upgradeHighQualityTip: '一旦升级为高质量模式,将无法切换回经济模式。', indexMethodEconomy: '经济', - indexMethodEconomyTip: '每个块使用 10 个关键词进行检索,不消耗 tokens,但会降低检索准确性。', + indexMethodEconomyTip: '每个块使用 {{count}} 个关键词进行检索,不消耗 tokens,但会降低检索准确性。', + numberOfKeywords: '关键词数量', embeddingModel: 'Embedding 模型', embeddingModelTip: '修改 Embedding 模型,请去', embeddingModelTipLink: '设置', diff --git a/web/models/datasets.ts b/web/models/datasets.ts index 52a16b2396..dc804d91d8 100644 --- a/web/models/datasets.ts +++ b/web/models/datasets.ts @@ -79,6 +79,7 @@ export type DataSet = { } built_in_field_enabled: boolean doc_metadata?: MetadataInDoc[] + keyword_number?: number } export type ExternalAPIItem = {