mirror of https://github.com/langgenius/dify.git
feat: knowledge base node
This commit is contained in:
parent
e86a3fc672
commit
3f52f491d7
|
|
@ -1,4 +1,5 @@
|
|||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type BoxProps = {
|
||||
|
|
@ -6,7 +7,7 @@ export type BoxProps = {
|
|||
children?: ReactNode
|
||||
withBorderBottom?: boolean
|
||||
}
|
||||
export const Box = ({
|
||||
export const Box = memo(({
|
||||
className,
|
||||
children,
|
||||
withBorderBottom,
|
||||
|
|
@ -21,4 +22,4 @@ export const Box = ({
|
|||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
|
|
@ -8,7 +9,7 @@ export type FieldTitleProps = {
|
|||
subTitle?: string | ReactNode
|
||||
tooltip?: string
|
||||
}
|
||||
export const FieldTitle = ({
|
||||
export const FieldTitle = memo(({
|
||||
title,
|
||||
operation,
|
||||
subTitle,
|
||||
|
|
@ -35,4 +36,4 @@ export const FieldTitle = ({
|
|||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import type { FieldTitleProps } from '.'
|
||||
import { FieldTitle } from '.'
|
||||
|
||||
|
|
@ -6,7 +7,7 @@ export type FieldProps = {
|
|||
fieldTitleProps: FieldTitleProps
|
||||
children: ReactNode
|
||||
}
|
||||
export const Field = ({
|
||||
export const Field = memo(({
|
||||
fieldTitleProps,
|
||||
children,
|
||||
}: FieldProps) => {
|
||||
|
|
@ -16,4 +17,4 @@ export const Field = ({
|
|||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
Box,
|
||||
Group,
|
||||
|
|
@ -13,7 +14,7 @@ type GroupWithBoxProps = {
|
|||
boxProps?: Omit<BoxProps, 'children'>
|
||||
groupProps?: Omit<GroupProps, 'children'>
|
||||
}
|
||||
export const GroupWithBox = ({
|
||||
export const GroupWithBox = memo(({
|
||||
children,
|
||||
boxProps,
|
||||
groupProps,
|
||||
|
|
@ -25,4 +26,4 @@ export const GroupWithBox = ({
|
|||
</Group>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { ReactNode } from 'react'
|
||||
import { memo } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type GroupProps = {
|
||||
|
|
@ -6,7 +7,7 @@ export type GroupProps = {
|
|||
children?: ReactNode
|
||||
withBorderBottom?: boolean
|
||||
}
|
||||
export const Group = ({
|
||||
export const Group = memo(({
|
||||
className,
|
||||
children,
|
||||
withBorderBottom,
|
||||
|
|
@ -21,4 +22,4 @@ export const Group = ({
|
|||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,13 +1,45 @@
|
|||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||
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 type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
|
||||
const EmbeddingModel = () => {
|
||||
const handleModelChange = () => {
|
||||
console.log('Model changed')
|
||||
}
|
||||
const handleCompletionParamsChange = () => {
|
||||
console.log('Completion parameters changed')
|
||||
type EmbeddingModelProps = {
|
||||
embeddingModel?: string
|
||||
embeddingModelProvider?: string
|
||||
onEmbeddingModelChange?: (model: {
|
||||
embeddingModel: string
|
||||
embeddingModelProvider: string
|
||||
}) => void
|
||||
}
|
||||
const EmbeddingModel = ({
|
||||
embeddingModel,
|
||||
embeddingModelProvider,
|
||||
onEmbeddingModelChange,
|
||||
}: EmbeddingModelProps) => {
|
||||
const {
|
||||
modelList: embeddingModelList,
|
||||
} = useModelListAndDefaultModel(ModelTypeEnum.textEmbedding)
|
||||
const embeddingModelConfig = useMemo(() => {
|
||||
if (!embeddingModel || !embeddingModelProvider)
|
||||
return undefined
|
||||
|
||||
return {
|
||||
providerName: embeddingModelProvider,
|
||||
modelName: embeddingModel,
|
||||
}
|
||||
}, [embeddingModel, embeddingModelProvider])
|
||||
|
||||
const handleRerankingModelChange = (model: DefaultModel) => {
|
||||
onEmbeddingModelChange?.({
|
||||
embeddingModelProvider: model.provider,
|
||||
embeddingModel: model.model,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Field
|
||||
|
|
@ -15,20 +47,12 @@ const EmbeddingModel = () => {
|
|||
title: 'Embedding Model',
|
||||
}}
|
||||
>
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
isInWorkflow
|
||||
isAdvancedMode={true}
|
||||
mode={'embedding'}
|
||||
provider={'openai'}
|
||||
completionParams={{}}
|
||||
modelId={'text-embedding-ada-002'}
|
||||
setModel={handleModelChange}
|
||||
onCompletionParamsChange={handleCompletionParamsChange}
|
||||
hideDebugWithMultipleModel
|
||||
debugWithMultipleModel={false}
|
||||
<ModelSelector
|
||||
defaultModel={embeddingModelConfig && { provider: embeddingModelConfig.providerName, model: embeddingModelConfig.modelName }}
|
||||
modelList={embeddingModelList}
|
||||
onSelect={handleRerankingModelChange}
|
||||
/>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
export default EmbeddingModel
|
||||
export default memo(EmbeddingModel)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { memo } from 'react'
|
||||
import VarReferencePicker from '@/app/components/workflow/nodes/_base/components/variable/var-reference-picker'
|
||||
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||
|
||||
const EmbeddingModel = () => {
|
||||
const InputVariable = () => {
|
||||
const handleChange = () => {
|
||||
console.log('')
|
||||
}
|
||||
|
|
@ -23,4 +24,4 @@ const EmbeddingModel = () => {
|
|||
</Field>
|
||||
)
|
||||
}
|
||||
export default EmbeddingModel
|
||||
export default memo(InputVariable)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useMemo } from 'react'
|
||||
import {
|
||||
FullTextSearch,
|
||||
HybridSearch,
|
||||
|
|
@ -13,53 +14,64 @@ import type {
|
|||
} from './type'
|
||||
|
||||
export const useRetrievalSetting = () => {
|
||||
const VectorSearchOption: Option = {
|
||||
id: RetrievalSearchMethodEnum.semantic,
|
||||
icon: VectorSearch as any,
|
||||
title: 'Vector Search',
|
||||
description: 'Generate query embeddings and search for the text chunk most similar to its vector representation.',
|
||||
effectColor: 'purple',
|
||||
}
|
||||
const FullTextSearchOption: Option = {
|
||||
id: RetrievalSearchMethodEnum.fullText,
|
||||
icon: FullTextSearch as any,
|
||||
title: 'Full-Text Search',
|
||||
description: 'Execute full-text search and vector searches simultaneously, re-rank to select the best match for the user\'s query. Users can choose to set weights or configure to a Rerank model.',
|
||||
effectColor: 'purple',
|
||||
}
|
||||
const HybridSearchOption: Option = {
|
||||
id: RetrievalSearchMethodEnum.hybrid,
|
||||
icon: HybridSearch as any,
|
||||
title: 'Hybrid Search',
|
||||
description: 'Execute full-text search and vector searches simultaneously, re-rank to select the best match for the user\'s query. Users can choose to set weights or configure to a Rerank model.',
|
||||
effectColor: 'purple',
|
||||
}
|
||||
const VectorSearchOption: Option = useMemo(() => {
|
||||
return {
|
||||
id: RetrievalSearchMethodEnum.semantic,
|
||||
icon: VectorSearch as any,
|
||||
title: 'Vector Search',
|
||||
description: 'Generate query embeddings and search for the text chunk most similar to its vector representation.',
|
||||
effectColor: 'purple',
|
||||
}
|
||||
}, [])
|
||||
const FullTextSearchOption: Option = useMemo(() => {
|
||||
return {
|
||||
id: RetrievalSearchMethodEnum.fullText,
|
||||
icon: FullTextSearch as any,
|
||||
title: 'Full-Text Search',
|
||||
description: 'Execute full-text search and vector searches simultaneously, re-rank to select the best match for the user\'s query. Users can choose to set weights or configure to a Rerank model.',
|
||||
effectColor: 'purple',
|
||||
}
|
||||
}, [])
|
||||
const HybridSearchOption: Option = useMemo(() => {
|
||||
return {
|
||||
id: RetrievalSearchMethodEnum.hybrid,
|
||||
icon: HybridSearch as any,
|
||||
title: 'Hybrid Search',
|
||||
description: 'Execute full-text search and vector searches simultaneously, re-rank to select the best match for the user\'s query. Users can choose to set weights or configure to a Rerank model.',
|
||||
effectColor: 'purple',
|
||||
}
|
||||
}, [])
|
||||
|
||||
const options = [
|
||||
const WeightedScoreModeOption: HybridSearchModeOption = useMemo(() => {
|
||||
return {
|
||||
id: HybridSearchModeEnum.WeightedScore,
|
||||
title: 'Weighted Score',
|
||||
description: 'By adjusting the weights assigned, this rerank strategy determines whether to prioritize semantic or keyword matching.',
|
||||
}
|
||||
}, [])
|
||||
const RerankModelModeOption: HybridSearchModeOption = useMemo(() => {
|
||||
return {
|
||||
id: HybridSearchModeEnum.RerankingModel,
|
||||
title: 'Rerank Model',
|
||||
description: 'Rerank model will reorder the candidate document list based on the semantic match with user query, improving the results of semantic ranking.',
|
||||
}
|
||||
}, [])
|
||||
|
||||
return useMemo(() => ({
|
||||
options: [
|
||||
VectorSearchOption,
|
||||
FullTextSearchOption,
|
||||
HybridSearchOption,
|
||||
],
|
||||
hybridSearchModeOptions: [
|
||||
WeightedScoreModeOption,
|
||||
RerankModelModeOption,
|
||||
],
|
||||
}), [
|
||||
VectorSearchOption,
|
||||
FullTextSearchOption,
|
||||
HybridSearchOption,
|
||||
]
|
||||
|
||||
const WeightedScoreModeOption: HybridSearchModeOption = {
|
||||
id: HybridSearchModeEnum.WeightedScore,
|
||||
title: 'Weighted Score',
|
||||
description: 'By adjusting the weights assigned, this rerank strategy determines whether to prioritize semantic or keyword matching.',
|
||||
}
|
||||
|
||||
const RerankModelModeOption: HybridSearchModeOption = {
|
||||
id: HybridSearchModeEnum.RerankingModel,
|
||||
title: 'Rerank Model',
|
||||
description: 'Rerank model will reorder the candidate document list based on the semantic match with user query, improving the results of semantic ranking.',
|
||||
}
|
||||
|
||||
const hybridSearchModeOptions = [
|
||||
WeightedScoreModeOption,
|
||||
RerankModelModeOption,
|
||||
]
|
||||
|
||||
return {
|
||||
options,
|
||||
hybridSearchModeOptions,
|
||||
}
|
||||
])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,28 @@
|
|||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||
import cn from '@/utils/classnames'
|
||||
import WeightedScoreComponent from '@/app/components/app/configuration/dataset-config/params-config/weighted-score'
|
||||
import { DEFAULT_WEIGHTED_SCORE } from '@/models/datasets'
|
||||
import {
|
||||
import type {
|
||||
HybridSearchModeEnum,
|
||||
RetrievalSearchMethodEnum,
|
||||
} from '../../types'
|
||||
import type {
|
||||
RerankingModel,
|
||||
WeightedScore,
|
||||
} from '../../types'
|
||||
import OptionCard from '../option-card'
|
||||
import { useRetrievalSetting } from './hooks'
|
||||
import type { Option } from './type'
|
||||
import TopKAndScoreThreshold from './top-k-and-score-threshold'
|
||||
import RerankingModelSelector from './reranking-model-selector'
|
||||
import type { TopKAndScoreThresholdProps } from './top-k-and-score-threshold'
|
||||
import type { RerankingModelSelectorProps } from './reranking-model-selector'
|
||||
import SearchMethodOption from './search-method-option'
|
||||
|
||||
type RetrievalSettingProps = {
|
||||
searchMethod: RetrievalSearchMethodEnum
|
||||
onRetrievalSearchMethodChange: (value: RetrievalSearchMethodEnum) => void
|
||||
hybridSearchMode: HybridSearchModeEnum
|
||||
onHybridSearchModeChange: (value: HybridSearchModeEnum) => void
|
||||
rerankingModel?: RerankingModel
|
||||
onRerankingModelChange: (model: RerankingModel) => void
|
||||
weightedScore?: WeightedScore
|
||||
onWeightedScoreChange: (value: { value: number[] }) => void
|
||||
}
|
||||
} & RerankingModelSelectorProps & TopKAndScoreThresholdProps
|
||||
|
||||
const RetrievalSetting = ({
|
||||
searchMethod,
|
||||
onRetrievalSearchMethodChange,
|
||||
|
|
@ -39,101 +32,18 @@ const RetrievalSetting = ({
|
|||
onWeightedScoreChange,
|
||||
rerankingModel,
|
||||
onRerankingModelChange,
|
||||
topK,
|
||||
onTopKChange,
|
||||
scoreThreshold,
|
||||
onScoreThresholdChange,
|
||||
isScoreThresholdEnabled,
|
||||
onScoreThresholdEnabledChange,
|
||||
}: RetrievalSettingProps) => {
|
||||
const {
|
||||
options,
|
||||
hybridSearchModeOptions,
|
||||
} = useRetrievalSetting()
|
||||
|
||||
const renderOptionCard = useCallback((option: Option) => {
|
||||
const Icon = option.icon
|
||||
const isActive = searchMethod === option.id
|
||||
const isHybridSearch = searchMethod === RetrievalSearchMethodEnum.hybrid
|
||||
const isHybridSearchWeightedScoreMode = hybridSearchMode === HybridSearchModeEnum.WeightedScore
|
||||
const weightedScoreValue = (() => {
|
||||
const sematicWeightedScore = weightedScore?.vector_setting.vector_weight ?? DEFAULT_WEIGHTED_SCORE.other.semantic
|
||||
const keywordWeightedScore = weightedScore?.keyword_setting.keyword_weight ?? DEFAULT_WEIGHTED_SCORE.other.keyword
|
||||
const mergedValue = [sematicWeightedScore, keywordWeightedScore]
|
||||
|
||||
return {
|
||||
value: mergedValue,
|
||||
}
|
||||
})()
|
||||
|
||||
return (
|
||||
<OptionCard
|
||||
key={option.id}
|
||||
id={option.id}
|
||||
icon={
|
||||
<Icon
|
||||
className={cn(
|
||||
'h-[15px] w-[15px] text-text-tertiary',
|
||||
isActive && 'text-util-colors-purple-purple-600',
|
||||
)}
|
||||
/>
|
||||
}
|
||||
title={option.title}
|
||||
description={option.description}
|
||||
effectColor={option.effectColor}
|
||||
isRecommended={option.id === RetrievalSearchMethodEnum.hybrid}
|
||||
onClick={onRetrievalSearchMethodChange}
|
||||
showChildren={isActive}
|
||||
showHighlightBorder={isActive}
|
||||
showEffectColor={isActive}
|
||||
>
|
||||
<div className='space-y-3'>
|
||||
{
|
||||
isHybridSearch && (
|
||||
<div className='space-y-1'>
|
||||
{
|
||||
hybridSearchModeOptions.map(hybridOption => (
|
||||
<OptionCard
|
||||
key={hybridOption.id}
|
||||
id={hybridOption.id}
|
||||
className='p-3'
|
||||
title={hybridOption.title}
|
||||
description={hybridOption.description}
|
||||
showRadio
|
||||
radioIsActive={hybridOption.id === hybridSearchMode}
|
||||
onClick={onHybridSearchModeChange}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
isHybridSearch && isHybridSearchWeightedScoreMode && (
|
||||
<WeightedScoreComponent
|
||||
value={weightedScoreValue}
|
||||
onChange={onWeightedScoreChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!(isHybridSearch && hybridSearchMode === HybridSearchModeEnum.WeightedScore) && (
|
||||
<RerankingModelSelector
|
||||
rerankingModel={rerankingModel}
|
||||
onRerankingModelChange={onRerankingModelChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<TopKAndScoreThreshold />
|
||||
</div>
|
||||
</OptionCard>
|
||||
)
|
||||
}, [
|
||||
searchMethod,
|
||||
onRetrievalSearchMethodChange,
|
||||
hybridSearchModeOptions,
|
||||
hybridSearchMode,
|
||||
onHybridSearchModeChange,
|
||||
rerankingModel,
|
||||
onRerankingModelChange,
|
||||
weightedScore,
|
||||
onWeightedScoreChange,
|
||||
])
|
||||
|
||||
return (
|
||||
<Field
|
||||
fieldTitleProps={{
|
||||
|
|
@ -154,7 +64,29 @@ const RetrievalSetting = ({
|
|||
}}
|
||||
>
|
||||
<div className='space-y-1'>
|
||||
{options.map(renderOptionCard)}
|
||||
{
|
||||
options.map(option => (
|
||||
<SearchMethodOption
|
||||
key={option.id}
|
||||
option={option}
|
||||
hybridSearchModeOptions={hybridSearchModeOptions}
|
||||
searchMethod={searchMethod}
|
||||
onRetrievalSearchMethodChange={onRetrievalSearchMethodChange}
|
||||
hybridSearchMode={hybridSearchMode}
|
||||
onHybridSearchModeChange={onHybridSearchModeChange}
|
||||
weightedScore={weightedScore}
|
||||
onWeightedScoreChange={onWeightedScoreChange}
|
||||
topK={topK}
|
||||
onTopKChange={onTopKChange}
|
||||
scoreThreshold={scoreThreshold}
|
||||
onScoreThresholdChange={onScoreThresholdChange}
|
||||
isScoreThresholdEnabled={isScoreThresholdEnabled}
|
||||
onScoreThresholdEnabledChange={onScoreThresholdEnabledChange}
|
||||
rerankingModel={rerankingModel}
|
||||
onRerankingModelChange={onRerankingModelChange}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Field>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { DefaultModel } from '@/app/components/header/account-setting/model
|
|||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { RerankingModel } from '../../types'
|
||||
|
||||
type RerankingModelSelectorProps = {
|
||||
export type RerankingModelSelectorProps = {
|
||||
rerankingModel?: RerankingModel
|
||||
onRerankingModelChange?: (model: RerankingModel) => void
|
||||
}
|
||||
|
|
@ -24,8 +24,8 @@ const RerankingModelSelector = ({
|
|||
return undefined
|
||||
|
||||
return {
|
||||
provider_name: rerankingModel.reranking_provider_name,
|
||||
model_name: rerankingModel.reranking_model_name,
|
||||
providerName: rerankingModel.reranking_provider_name,
|
||||
modelName: rerankingModel.reranking_model_name,
|
||||
}
|
||||
}, [rerankingModel])
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ const RerankingModelSelector = ({
|
|||
|
||||
return (
|
||||
<ModelSelector
|
||||
defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }}
|
||||
defaultModel={rerankModel && { provider: rerankModel.providerName, model: rerankModel.modelName }}
|
||||
modelList={rerankModelList}
|
||||
onSelect={handleRerankingModelChange}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import WeightedScoreComponent from '@/app/components/app/configuration/dataset-config/params-config/weighted-score'
|
||||
import { DEFAULT_WEIGHTED_SCORE } from '@/models/datasets'
|
||||
import {
|
||||
HybridSearchModeEnum,
|
||||
RetrievalSearchMethodEnum,
|
||||
} from '../../types'
|
||||
import type {
|
||||
WeightedScore,
|
||||
} from '../../types'
|
||||
import OptionCard from '../option-card'
|
||||
import type {
|
||||
HybridSearchModeOption,
|
||||
Option,
|
||||
} from './type'
|
||||
import type { TopKAndScoreThresholdProps } from './top-k-and-score-threshold'
|
||||
import TopKAndScoreThreshold from './top-k-and-score-threshold'
|
||||
import type { RerankingModelSelectorProps } from './reranking-model-selector'
|
||||
import RerankingModelSelector from './reranking-model-selector'
|
||||
|
||||
type SearchMethodOptionProps = {
|
||||
option: Option
|
||||
hybridSearchModeOptions: HybridSearchModeOption[]
|
||||
searchMethod: RetrievalSearchMethodEnum
|
||||
onRetrievalSearchMethodChange: (value: RetrievalSearchMethodEnum) => void
|
||||
hybridSearchMode: HybridSearchModeEnum
|
||||
onHybridSearchModeChange: (value: HybridSearchModeEnum) => void
|
||||
weightedScore?: WeightedScore
|
||||
onWeightedScoreChange: (value: { value: number[] }) => void
|
||||
} & RerankingModelSelectorProps & TopKAndScoreThresholdProps
|
||||
const SearchMethodOption = ({
|
||||
option,
|
||||
hybridSearchModeOptions,
|
||||
searchMethod,
|
||||
onRetrievalSearchMethodChange,
|
||||
hybridSearchMode,
|
||||
onHybridSearchModeChange,
|
||||
weightedScore,
|
||||
onWeightedScoreChange,
|
||||
rerankingModel,
|
||||
onRerankingModelChange,
|
||||
topK,
|
||||
onTopKChange,
|
||||
scoreThreshold,
|
||||
onScoreThresholdChange,
|
||||
isScoreThresholdEnabled,
|
||||
onScoreThresholdEnabledChange,
|
||||
}: SearchMethodOptionProps) => {
|
||||
const Icon = option.icon
|
||||
const isActive = searchMethod === option.id
|
||||
const isHybridSearch = option.id === RetrievalSearchMethodEnum.hybrid
|
||||
const isHybridSearchWeightedScoreMode = hybridSearchMode === HybridSearchModeEnum.WeightedScore
|
||||
|
||||
const weightedScoreValue = useMemo(() => {
|
||||
const sematicWeightedScore = weightedScore?.vector_setting.vector_weight ?? DEFAULT_WEIGHTED_SCORE.other.semantic
|
||||
const keywordWeightedScore = weightedScore?.keyword_setting.keyword_weight ?? DEFAULT_WEIGHTED_SCORE.other.keyword
|
||||
const mergedValue = [sematicWeightedScore, keywordWeightedScore]
|
||||
|
||||
return {
|
||||
value: mergedValue,
|
||||
}
|
||||
}, [weightedScore])
|
||||
|
||||
const icon = useMemo(() => {
|
||||
return (
|
||||
<Icon
|
||||
className={cn(
|
||||
'h-[15px] w-[15px] text-text-tertiary',
|
||||
isActive && 'text-util-colors-purple-purple-600',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}, [isActive, Icon])
|
||||
|
||||
return (
|
||||
<OptionCard
|
||||
key={option.id}
|
||||
id={option.id}
|
||||
icon={icon}
|
||||
title={option.title}
|
||||
description={option.description}
|
||||
effectColor={option.effectColor}
|
||||
isRecommended={option.id === RetrievalSearchMethodEnum.hybrid}
|
||||
onClick={onRetrievalSearchMethodChange}
|
||||
showChildren={isActive}
|
||||
showHighlightBorder={isActive}
|
||||
showEffectColor={isActive}
|
||||
>
|
||||
<div className='space-y-3'>
|
||||
{
|
||||
isHybridSearch && (
|
||||
<div className='space-y-1'>
|
||||
{
|
||||
hybridSearchModeOptions.map(hybridOption => (
|
||||
<OptionCard
|
||||
key={hybridOption.id}
|
||||
id={hybridOption.id}
|
||||
className='p-3'
|
||||
title={hybridOption.title}
|
||||
description={hybridOption.description}
|
||||
showRadio
|
||||
radioIsActive={hybridOption.id === hybridSearchMode}
|
||||
onClick={onHybridSearchModeChange}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
isHybridSearch && isHybridSearchWeightedScoreMode && (
|
||||
<WeightedScoreComponent
|
||||
value={weightedScoreValue}
|
||||
onChange={onWeightedScoreChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!(isHybridSearch && hybridSearchMode === HybridSearchModeEnum.WeightedScore) && (
|
||||
<RerankingModelSelector
|
||||
rerankingModel={rerankingModel}
|
||||
onRerankingModelChange={onRerankingModelChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<TopKAndScoreThreshold
|
||||
topK={topK}
|
||||
onTopKChange={onTopKChange}
|
||||
scoreThreshold={scoreThreshold}
|
||||
onScoreThresholdChange={onScoreThresholdChange}
|
||||
isScoreThresholdEnabled={isScoreThresholdEnabled}
|
||||
onScoreThresholdEnabledChange={onScoreThresholdEnabledChange}
|
||||
/>
|
||||
</div>
|
||||
</OptionCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(SearchMethodOption)
|
||||
|
|
@ -3,7 +3,36 @@ import Tooltip from '@/app/components/base/tooltip'
|
|||
import Input from '@/app/components/base/input'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
|
||||
const TopKAndScoreThreshold = () => {
|
||||
export type TopKAndScoreThresholdProps = {
|
||||
topK: number
|
||||
onTopKChange: (value: number) => void
|
||||
scoreThreshold?: number
|
||||
onScoreThresholdChange?: (value: number) => void
|
||||
isScoreThresholdEnabled?: boolean
|
||||
onScoreThresholdEnabledChange?: (value: boolean) => void
|
||||
}
|
||||
const TopKAndScoreThreshold = ({
|
||||
topK,
|
||||
onTopKChange,
|
||||
scoreThreshold,
|
||||
onScoreThresholdChange,
|
||||
isScoreThresholdEnabled,
|
||||
onScoreThresholdEnabledChange,
|
||||
}: TopKAndScoreThresholdProps) => {
|
||||
const handleTopKChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = Number(e.target.value)
|
||||
if (Number.isNaN(value))
|
||||
return
|
||||
onTopKChange?.(value)
|
||||
}
|
||||
|
||||
const handleScoreThresholdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = Number(e.target.value)
|
||||
if (Number.isNaN(value))
|
||||
return
|
||||
onScoreThresholdChange?.(value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<div>
|
||||
|
|
@ -16,12 +45,16 @@ const TopKAndScoreThreshold = () => {
|
|||
</div>
|
||||
<Input
|
||||
type='number'
|
||||
value={topK}
|
||||
onChange={handleTopKChange}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className='mb-0.5 flex h-6 items-center'>
|
||||
<Switch
|
||||
className='mr-2'
|
||||
defaultValue={isScoreThresholdEnabled}
|
||||
onChange={onScoreThresholdEnabledChange}
|
||||
/>
|
||||
<div className='system-sm-medium grow truncate text-text-secondary'>
|
||||
Score Threshold
|
||||
|
|
@ -33,6 +66,8 @@ const TopKAndScoreThreshold = () => {
|
|||
</div>
|
||||
<Input
|
||||
type='number'
|
||||
value={scoreThreshold}
|
||||
onChange={handleScoreThresholdChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,28 @@ export const useConfig = (id: string) => {
|
|||
handleNodeDataUpdate({ keyword_number: keywordNumber })
|
||||
}, [handleNodeDataUpdate])
|
||||
|
||||
const handleEmbeddingModelChange = useCallback(({
|
||||
embeddingModel,
|
||||
embeddingModelProvider,
|
||||
}: {
|
||||
embeddingModel: string
|
||||
embeddingModelProvider: string
|
||||
}) => {
|
||||
const nodeData = getNodeData()
|
||||
handleNodeDataUpdate({
|
||||
embedding_model: embeddingModel,
|
||||
embedding_model_provider: embeddingModelProvider,
|
||||
retrieval_model: {
|
||||
...nodeData?.data.retrieval_model,
|
||||
vector_setting: {
|
||||
...nodeData?.data.retrieval_model.vector_setting,
|
||||
embedding_provider_name: embeddingModelProvider,
|
||||
embedding_model_name: embeddingModel,
|
||||
},
|
||||
},
|
||||
})
|
||||
}, [getNodeData, handleNodeDataUpdate])
|
||||
|
||||
const handleRetrievalSearchMethodChange = useCallback((searchMethod: RetrievalSearchMethodEnum) => {
|
||||
const nodeData = getNodeData()
|
||||
handleNodeDataUpdate({
|
||||
|
|
@ -95,13 +117,47 @@ export const useConfig = (id: string) => {
|
|||
})
|
||||
}, [getNodeData, handleNodeDataUpdate])
|
||||
|
||||
const handleTopKChange = useCallback((topK: number) => {
|
||||
const nodeData = getNodeData()
|
||||
handleNodeDataUpdate({
|
||||
retrieval_model: {
|
||||
...nodeData?.data.retrieval_model,
|
||||
top_k: topK,
|
||||
},
|
||||
})
|
||||
}, [getNodeData, handleNodeDataUpdate])
|
||||
|
||||
const handleScoreThresholdChange = useCallback((scoreThreshold: number) => {
|
||||
const nodeData = getNodeData()
|
||||
handleNodeDataUpdate({
|
||||
retrieval_model: {
|
||||
...nodeData?.data.retrieval_model,
|
||||
score_threshold: scoreThreshold,
|
||||
},
|
||||
})
|
||||
}, [getNodeData, handleNodeDataUpdate])
|
||||
|
||||
const handleScoreThresholdEnabledChange = useCallback((isEnabled: boolean) => {
|
||||
const nodeData = getNodeData()
|
||||
handleNodeDataUpdate({
|
||||
retrieval_model: {
|
||||
...nodeData?.data.retrieval_model,
|
||||
score_threshold_enabled: isEnabled,
|
||||
},
|
||||
})
|
||||
}, [getNodeData, handleNodeDataUpdate])
|
||||
|
||||
return {
|
||||
handleChunkStructureChange,
|
||||
handleIndexMethodChange,
|
||||
handleKeywordNumberChange,
|
||||
handleEmbeddingModelChange,
|
||||
handleRetrievalSearchMethodChange,
|
||||
handleHybridSearchModeChange,
|
||||
handleWeighedScoreChange,
|
||||
handleRerankingModelChange,
|
||||
handleTopKChange,
|
||||
handleScoreThresholdChange,
|
||||
handleScoreThresholdEnabledChange,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,18 @@ import type { FC } from 'react'
|
|||
import { memo } from 'react'
|
||||
import type { KnowledgeBaseNodeType } from './types'
|
||||
import type { NodeProps } from '@/app/components/workflow/types'
|
||||
const Node: FC<NodeProps<KnowledgeBaseNodeType>> = () => {
|
||||
|
||||
const Node: FC<NodeProps<KnowledgeBaseNodeType>> = ({ data }) => {
|
||||
return (
|
||||
<div className='mb-1 px-3 py-1'>
|
||||
KnowledgeBase
|
||||
<div className='mb-1 space-y-0.5 px-3 py-1'>
|
||||
<div className='flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1.5'>
|
||||
<div className='system-xs-medium-uppercase mr-2 shrink-0 text-text-tertiary'>Index method</div>
|
||||
<div className='system-xs-medium grow truncate text-right text-text-secondary' title={data.indexing_technique}>{data.indexing_technique}</div>
|
||||
</div>
|
||||
<div className='flex h-6 items-center rounded-md bg-workflow-block-parma-bg px-1.5'>
|
||||
<div className='system-xs-medium-uppercase mr-2 shrink-0 text-text-tertiary'>Retrieval Method</div>
|
||||
<div className='system-xs-medium grow truncate text-right text-text-secondary' title={data.retrieval_model.search_method}>{data.retrieval_model.search_method}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,14 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
|||
handleChunkStructureChange,
|
||||
handleIndexMethodChange,
|
||||
handleKeywordNumberChange,
|
||||
handleEmbeddingModelChange,
|
||||
handleRetrievalSearchMethodChange,
|
||||
handleHybridSearchModeChange,
|
||||
handleWeighedScoreChange,
|
||||
handleRerankingModelChange,
|
||||
handleTopKChange,
|
||||
handleScoreThresholdChange,
|
||||
handleScoreThresholdEnabledChange,
|
||||
} = useConfig(id)
|
||||
|
||||
return (
|
||||
|
|
@ -57,7 +61,11 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
|||
/>
|
||||
{
|
||||
data.indexing_technique === IndexMethodEnum.QUALIFIED && (
|
||||
<EmbeddingModel />
|
||||
<EmbeddingModel
|
||||
embeddingModel={data.embedding_model}
|
||||
embeddingModelProvider={data.embedding_model_provider}
|
||||
onEmbeddingModelChange={handleEmbeddingModelChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<div className='pt-1'>
|
||||
|
|
@ -72,6 +80,12 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
|||
onWeightedScoreChange={handleWeighedScoreChange}
|
||||
rerankingModel={data.retrieval_model.reranking_model}
|
||||
onRerankingModelChange={handleRerankingModelChange}
|
||||
topK={data.retrieval_model.top_k}
|
||||
onTopKChange={handleTopKChange}
|
||||
scoreThreshold={data.retrieval_model.score_threshold}
|
||||
onScoreThresholdChange={handleScoreThresholdChange}
|
||||
isScoreThresholdEnabled={data.retrieval_model.score_threshold_enabled}
|
||||
onScoreThresholdEnabledChange={handleScoreThresholdEnabledChange}
|
||||
/>
|
||||
</div>
|
||||
</GroupWithBox>
|
||||
|
|
|
|||
Loading…
Reference in New Issue