mirror of
https://github.com/langgenius/dify.git
synced 2026-04-28 20:17:29 +08:00
feat: knowledge base node
This commit is contained in:
parent
12c060b795
commit
a478d95950
@ -4,7 +4,7 @@ import {
|
|||||||
} from '@/app/components/workflow/context'
|
} from '@/app/components/workflow/context'
|
||||||
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
|
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
|
||||||
import { generateNewNode } from '@/app/components/workflow/utils'
|
import { generateNewNode } from '@/app/components/workflow/utils'
|
||||||
import dataSourceNodeDefault from '@/app/components/workflow/nodes/data-source/default'
|
import knowledgeBaseNodeDefault from '@/app/components/workflow/nodes/knowledge-base/default'
|
||||||
import {
|
import {
|
||||||
NODE_WIDTH_X_OFFSET,
|
NODE_WIDTH_X_OFFSET,
|
||||||
START_INITIAL_POSITION,
|
START_INITIAL_POSITION,
|
||||||
@ -13,11 +13,11 @@ import { createRagPipelineSliceSlice } from './store'
|
|||||||
import RagPipelineMain from './components/rag-pipeline-main'
|
import RagPipelineMain from './components/rag-pipeline-main'
|
||||||
|
|
||||||
const RagPipeline = () => {
|
const RagPipeline = () => {
|
||||||
const { newNode: DataSourceNode } = generateNewNode({
|
const { newNode: knowledgeBaseNode } = generateNewNode({
|
||||||
data: {
|
data: {
|
||||||
type: dataSourceNodeDefault.metaData.type,
|
type: knowledgeBaseNodeDefault.metaData.type,
|
||||||
title: 'data-source',
|
title: 'knowledge-base',
|
||||||
...dataSourceNodeDefault.defaultValue,
|
...knowledgeBaseNodeDefault.defaultValue,
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
x: START_INITIAL_POSITION.x + NODE_WIDTH_X_OFFSET,
|
x: START_INITIAL_POSITION.x + NODE_WIDTH_X_OFFSET,
|
||||||
@ -30,11 +30,11 @@ const RagPipeline = () => {
|
|||||||
>
|
>
|
||||||
<WorkflowWithDefaultContext
|
<WorkflowWithDefaultContext
|
||||||
edges={[]}
|
edges={[]}
|
||||||
nodes={[DataSourceNode]}
|
nodes={[knowledgeBaseNode]}
|
||||||
>
|
>
|
||||||
<RagPipelineMain
|
<RagPipelineMain
|
||||||
edges={[]}
|
edges={[]}
|
||||||
nodes={[DataSourceNode]}
|
nodes={[knowledgeBaseNode]}
|
||||||
/>
|
/>
|
||||||
</WorkflowWithDefaultContext>
|
</WorkflowWithDefaultContext>
|
||||||
</WorkflowContextProvider>
|
</WorkflowContextProvider>
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import {
|
import {
|
||||||
GeneralChunk,
|
GeneralChunk,
|
||||||
ParentChildChunk,
|
ParentChildChunk,
|
||||||
QuestionAndAnswer,
|
QuestionAndAnswer,
|
||||||
} from '@/app/components/base/icons/src/vender/knowledge'
|
} from '@/app/components/base/icons/src/vender/knowledge'
|
||||||
|
import { ChunkStructureEnum } from '../../types'
|
||||||
import type { Option } from './type'
|
import type { Option } from './type'
|
||||||
|
|
||||||
export const useChunkStructure = () => {
|
export const useChunkStructure = () => {
|
||||||
const [chunk, setChunk] = useState('general')
|
|
||||||
const GeneralOption: Option = {
|
const GeneralOption: Option = {
|
||||||
key: 'general',
|
id: ChunkStructureEnum.general,
|
||||||
icon: <GeneralChunk className='h-[18px] w-[18px] text-util-colors-indigo-indigo-600' />,
|
icon: <GeneralChunk className='h-[18px] w-[18px] text-util-colors-indigo-indigo-600' />,
|
||||||
title: 'General',
|
title: 'General',
|
||||||
description: 'General text chunking mode, the chunks retrieved and recalled are the same.',
|
description: 'General text chunking mode, the chunks retrieved and recalled are the same.',
|
||||||
@ -17,7 +16,7 @@ export const useChunkStructure = () => {
|
|||||||
showEffectColor: true,
|
showEffectColor: true,
|
||||||
}
|
}
|
||||||
const ParentChildOption: Option = {
|
const ParentChildOption: Option = {
|
||||||
key: 'parent-child',
|
id: ChunkStructureEnum.parent_child,
|
||||||
icon: <ParentChildChunk className='h-[18px] w-[18px] text-util-colors-blue-light-blue-light-500' />,
|
icon: <ParentChildChunk className='h-[18px] w-[18px] text-util-colors-blue-light-blue-light-500' />,
|
||||||
title: 'Parent-Child',
|
title: 'Parent-Child',
|
||||||
description: 'Parent-child text chunking mode, the chunks retrieved and recalled are different.',
|
description: 'Parent-child text chunking mode, the chunks retrieved and recalled are different.',
|
||||||
@ -25,16 +24,16 @@ export const useChunkStructure = () => {
|
|||||||
showEffectColor: true,
|
showEffectColor: true,
|
||||||
}
|
}
|
||||||
const QuestionAnswerOption: Option = {
|
const QuestionAnswerOption: Option = {
|
||||||
key: 'question-answer',
|
id: ChunkStructureEnum.question_answer,
|
||||||
icon: <QuestionAndAnswer className='h-[18px] w-[18px] text-text-tertiary' />,
|
icon: <QuestionAndAnswer className='h-[18px] w-[18px] text-text-tertiary' />,
|
||||||
title: 'Question-Answer',
|
title: 'Question-Answer',
|
||||||
description: 'Question-answer text chunking mode, the chunks retrieved and recalled are different.',
|
description: 'Question-answer text chunking mode, the chunks retrieved and recalled are different.',
|
||||||
}
|
}
|
||||||
|
|
||||||
const optionMap: Record<string, Option> = {
|
const optionMap: Record<ChunkStructureEnum, Option> = {
|
||||||
'general': GeneralOption,
|
[ChunkStructureEnum.general]: GeneralOption,
|
||||||
'parent-child': ParentChildOption,
|
[ChunkStructureEnum.parent_child]: ParentChildOption,
|
||||||
'question-answer': QuestionAnswerOption,
|
[ChunkStructureEnum.question_answer]: QuestionAnswerOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
@ -46,7 +45,5 @@ export const useChunkStructure = () => {
|
|||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
optionMap,
|
optionMap,
|
||||||
chunk,
|
|
||||||
setChunk,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||||
|
import type { ChunkStructureEnum } from '../../types'
|
||||||
import OptionCard from '../option-card'
|
import OptionCard from '../option-card'
|
||||||
import Selector from './selector'
|
import Selector from './selector'
|
||||||
import { useChunkStructure } from './hooks'
|
import { useChunkStructure } from './hooks'
|
||||||
|
|
||||||
const ChunkStructure = () => {
|
type ChunkStructureProps = {
|
||||||
|
chunkStructure: ChunkStructureEnum
|
||||||
|
onChunkStructureChange: (value: ChunkStructureEnum) => void
|
||||||
|
}
|
||||||
|
const ChunkStructure = ({
|
||||||
|
chunkStructure,
|
||||||
|
onChunkStructureChange,
|
||||||
|
}: ChunkStructureProps) => {
|
||||||
const {
|
const {
|
||||||
chunk,
|
|
||||||
setChunk,
|
|
||||||
options,
|
options,
|
||||||
optionMap,
|
optionMap,
|
||||||
} = useChunkStructure()
|
} = useChunkStructure()
|
||||||
@ -19,15 +26,15 @@ const ChunkStructure = () => {
|
|||||||
operation: (
|
operation: (
|
||||||
<Selector
|
<Selector
|
||||||
options={options}
|
options={options}
|
||||||
value={chunk}
|
value={chunkStructure}
|
||||||
onChange={setChunk}
|
onChange={onChunkStructureChange}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OptionCard {...optionMap[chunk]} />
|
<OptionCard {...optionMap[chunkStructure]} />
|
||||||
</Field>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChunkStructure
|
export default memo(ChunkStructure)
|
||||||
|
|||||||
@ -5,13 +5,14 @@ import {
|
|||||||
PortalToFollowElemTrigger,
|
PortalToFollowElemTrigger,
|
||||||
} from '@/app/components/base/portal-to-follow-elem'
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
|
import type { ChunkStructureEnum } from '../../types'
|
||||||
import OptionCard from '../option-card'
|
import OptionCard from '../option-card'
|
||||||
import type { Option } from './type'
|
import type { Option } from './type'
|
||||||
|
|
||||||
type SelectorProps = {
|
type SelectorProps = {
|
||||||
options: Option[]
|
options: Option[]
|
||||||
value: string
|
value: ChunkStructureEnum
|
||||||
onChange: (key: string) => void
|
onChange: (key: ChunkStructureEnum) => void
|
||||||
}
|
}
|
||||||
const Selector = ({
|
const Selector = ({
|
||||||
options,
|
options,
|
||||||
@ -47,15 +48,16 @@ const Selector = ({
|
|||||||
{
|
{
|
||||||
options.map(option => (
|
options.map(option => (
|
||||||
<OptionCard
|
<OptionCard
|
||||||
key={option.key}
|
key={option.id}
|
||||||
|
id={option.id}
|
||||||
icon={option.icon}
|
icon={option.icon}
|
||||||
title={option.title}
|
title={option.title}
|
||||||
description={option.description}
|
description={option.description}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onChange(option.key)
|
onChange(option.id)
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
}}
|
}}
|
||||||
showHighlightBorder={value === option.key}
|
showHighlightBorder={value === option.id}
|
||||||
></OptionCard>
|
></OptionCard>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
|
import type { ChunkStructureEnum } from '../../types'
|
||||||
|
|
||||||
export type Option = {
|
export type Option = {
|
||||||
key: string
|
id: ChunkStructureEnum
|
||||||
icon: ReactNode
|
icon: ReactNode
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
import { useState } from 'react'
|
import {
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { RiQuestionLine } from '@remixicon/react'
|
import { RiQuestionLine } from '@remixicon/react'
|
||||||
import {
|
import {
|
||||||
@ -11,10 +14,33 @@ import Input from '@/app/components/base/input'
|
|||||||
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||||
import OptionCard from './option-card'
|
import OptionCard from './option-card'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
import { IndexMethodEnum } from '../types'
|
||||||
|
|
||||||
const IndexMethod = () => {
|
type IndexMethodProps = {
|
||||||
|
indexMethod: IndexMethodEnum
|
||||||
|
onIndexMethodChange: (value: IndexMethodEnum) => void
|
||||||
|
keywordNumber: number
|
||||||
|
onKeywordNumberChange: (value: number) => void
|
||||||
|
}
|
||||||
|
const IndexMethod = ({
|
||||||
|
indexMethod,
|
||||||
|
onIndexMethodChange,
|
||||||
|
keywordNumber,
|
||||||
|
onKeywordNumberChange,
|
||||||
|
}: IndexMethodProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [method, setMethod] = useState('high_quality')
|
const isHighQuality = indexMethod === IndexMethodEnum.QUALIFIED
|
||||||
|
const isEconomy = indexMethod === IndexMethodEnum.ECONOMICAL
|
||||||
|
|
||||||
|
const handleIndexMethodChange = useCallback((newIndexMethod: IndexMethodEnum) => {
|
||||||
|
onIndexMethodChange(newIndexMethod)
|
||||||
|
}, [onIndexMethodChange])
|
||||||
|
|
||||||
|
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = Number(e.target.value)
|
||||||
|
if (!Number.isNaN(value))
|
||||||
|
onKeywordNumberChange(value)
|
||||||
|
}, [onKeywordNumberChange])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
@ -23,37 +49,39 @@ const IndexMethod = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='space-y-1'>
|
<div className='space-y-1'>
|
||||||
<OptionCard
|
<OptionCard<IndexMethodEnum>
|
||||||
|
id={IndexMethodEnum.QUALIFIED}
|
||||||
icon={
|
icon={
|
||||||
<HighQuality
|
<HighQuality
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-[15px] w-[15px] text-text-tertiary',
|
'h-[15px] w-[15px] text-text-tertiary',
|
||||||
method === 'high_quality' && 'text-util-colors-orange-orange-500',
|
isHighQuality && 'text-util-colors-orange-orange-500',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
title={t('datasetCreation.stepTwo.qualified')}
|
title={t('datasetCreation.stepTwo.qualified')}
|
||||||
description={t('datasetSettings.form.indexMethodHighQualityTip')}
|
description={t('datasetSettings.form.indexMethodHighQualityTip')}
|
||||||
showHighlightBorder={method === 'high_quality'}
|
showHighlightBorder={isHighQuality}
|
||||||
onClick={() => setMethod('high_quality')}
|
onClick={handleIndexMethodChange}
|
||||||
isRecommended
|
isRecommended
|
||||||
></OptionCard>
|
></OptionCard>
|
||||||
<OptionCard
|
<OptionCard
|
||||||
|
id={IndexMethodEnum.ECONOMICAL}
|
||||||
icon={
|
icon={
|
||||||
<Economic
|
<Economic
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-[15px] w-[15px] text-text-tertiary',
|
'h-[15px] w-[15px] text-text-tertiary',
|
||||||
method === 'economy' && 'text-util-colors-indigo-indigo-500',
|
isEconomy && 'text-util-colors-indigo-indigo-500',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
title={t('datasetSettings.form.indexMethodEconomy')}
|
title={t('datasetSettings.form.indexMethodEconomy')}
|
||||||
description={t('datasetSettings.form.indexMethodEconomyTip')}
|
description={t('datasetSettings.form.indexMethodEconomyTip')}
|
||||||
showChildren={method === 'economy'}
|
showChildren={isEconomy}
|
||||||
showHighlightBorder={method === 'economy'}
|
showHighlightBorder={isEconomy}
|
||||||
onClick={() => setMethod('economy')}
|
onClick={handleIndexMethodChange}
|
||||||
effectColor='blue'
|
effectColor='blue'
|
||||||
showEffectColor={method === 'economy'}
|
showEffectColor={isEconomy}
|
||||||
>
|
>
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
<div className='flex grow items-center'>
|
<div className='flex grow items-center'>
|
||||||
@ -68,15 +96,15 @@ const IndexMethod = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Slider
|
<Slider
|
||||||
className='mr-3 w-24 shrink-0'
|
className='mr-3 w-24 shrink-0'
|
||||||
value={0}
|
value={keywordNumber}
|
||||||
onChange={() => {
|
onChange={onKeywordNumberChange}
|
||||||
console.log('change')
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
className='shrink-0'
|
className='shrink-0'
|
||||||
wrapperClassName='shrink-0 w-[72px]'
|
wrapperClassName='shrink-0 w-[72px]'
|
||||||
type='number'
|
type='number'
|
||||||
|
value={keywordNumber}
|
||||||
|
onChange={handleInputChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</OptionCard>
|
</OptionCard>
|
||||||
@ -85,4 +113,4 @@ const IndexMethod = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default IndexMethod
|
export default memo(IndexMethod)
|
||||||
|
|||||||
@ -16,7 +16,9 @@ const HEADER_EFFECT_MAP: Record<string, ReactNode> = {
|
|||||||
'orange': <OptionCardEffectOrange />,
|
'orange': <OptionCardEffectOrange />,
|
||||||
'purple': <OptionCardEffectPurple />,
|
'purple': <OptionCardEffectPurple />,
|
||||||
}
|
}
|
||||||
type OptionCardProps = {
|
type OptionCardProps<T> = {
|
||||||
|
id: T
|
||||||
|
className?: string
|
||||||
showHighlightBorder?: boolean
|
showHighlightBorder?: boolean
|
||||||
showRadio?: boolean
|
showRadio?: boolean
|
||||||
radioIsActive?: boolean
|
radioIsActive?: boolean
|
||||||
@ -28,9 +30,11 @@ type OptionCardProps = {
|
|||||||
showChildren?: boolean
|
showChildren?: boolean
|
||||||
effectColor?: string
|
effectColor?: string
|
||||||
showEffectColor?: boolean
|
showEffectColor?: boolean
|
||||||
onClick?: () => void
|
onClick?: (id: T) => void
|
||||||
}
|
}
|
||||||
const OptionCard = ({
|
const OptionCard = memo(({
|
||||||
|
id,
|
||||||
|
className,
|
||||||
showHighlightBorder,
|
showHighlightBorder,
|
||||||
showRadio,
|
showRadio,
|
||||||
radioIsActive,
|
radioIsActive,
|
||||||
@ -43,17 +47,18 @@ const OptionCard = ({
|
|||||||
effectColor,
|
effectColor,
|
||||||
showEffectColor,
|
showEffectColor,
|
||||||
onClick,
|
onClick,
|
||||||
}: OptionCardProps) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'cursor-pointer rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg',
|
'cursor-pointer rounded-xl border border-components-option-card-option-border bg-components-option-card-option-bg',
|
||||||
showHighlightBorder && 'border-[2px] border-components-option-card-option-selected-border',
|
showHighlightBorder && 'border-[2px] border-components-option-card-option-selected-border',
|
||||||
)}
|
)}
|
||||||
onClick={onClick}
|
onClick={() => onClick?.(id)}
|
||||||
>
|
>
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'relative flex rounded-t-xl p-2',
|
'relative flex rounded-t-xl p-2',
|
||||||
|
className,
|
||||||
)}>
|
)}>
|
||||||
{
|
{
|
||||||
effectColor && showEffectColor && (
|
effectColor && showEffectColor && (
|
||||||
@ -110,6 +115,6 @@ const OptionCard = ({
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}) as <T>(props: OptionCardProps<T>) => JSX.Element
|
||||||
|
|
||||||
export default memo(OptionCard)
|
export default OptionCard
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
import { FullTextSearch } from '@/app/components/base/icons/src/vender/knowledge'
|
|
||||||
import OptionCard from '../option-card'
|
|
||||||
|
|
||||||
const FullTextSearchCard = () => {
|
|
||||||
return (
|
|
||||||
<OptionCard
|
|
||||||
icon={<FullTextSearch className='h-[15px] w-[15px] text-text-tertiary' />}
|
|
||||||
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'
|
|
||||||
>
|
|
||||||
<div className='flex flex-col gap-2'>
|
|
||||||
<div>Vector Search Settings</div>
|
|
||||||
<div>Additional Settings</div>
|
|
||||||
</div>
|
|
||||||
</OptionCard>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FullTextSearchCard
|
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
FullTextSearch,
|
||||||
|
HybridSearch,
|
||||||
|
VectorSearch,
|
||||||
|
} from '@/app/components/base/icons/src/vender/knowledge'
|
||||||
|
import {
|
||||||
|
HybridSearchModeEnum,
|
||||||
|
RetrievalSearchMethodEnum,
|
||||||
|
} from '../../types'
|
||||||
|
import type {
|
||||||
|
HybridSearchModeOption,
|
||||||
|
Option,
|
||||||
|
} 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 options = [
|
||||||
|
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,21 +0,0 @@
|
|||||||
import { HybridSearch } from '@/app/components/base/icons/src/vender/knowledge'
|
|
||||||
import OptionCard from '../option-card'
|
|
||||||
|
|
||||||
const HybridSearchCard = () => {
|
|
||||||
return (
|
|
||||||
<OptionCard
|
|
||||||
icon={<HybridSearch className='h-[15px] w-[15px] text-text-tertiary' />}
|
|
||||||
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'
|
|
||||||
isRecommended
|
|
||||||
>
|
|
||||||
<div className='flex flex-col gap-2'>
|
|
||||||
<div>Vector Search Settings</div>
|
|
||||||
<div>Additional Settings</div>
|
|
||||||
</div>
|
|
||||||
</OptionCard>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HybridSearchCard
|
|
||||||
@ -1,9 +1,139 @@
|
|||||||
|
import {
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
} from 'react'
|
||||||
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
|
||||||
import VectorSearchCard from './vector-search'
|
import cn from '@/utils/classnames'
|
||||||
import FullTextSearchCard from './full-text-search'
|
import WeightedScoreComponent from '@/app/components/app/configuration/dataset-config/params-config/weighted-score'
|
||||||
import HybridSearchCard from './hybrid-search'
|
import { DEFAULT_WEIGHTED_SCORE } from '@/models/datasets'
|
||||||
|
import {
|
||||||
|
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'
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
const RetrievalSetting = ({
|
||||||
|
searchMethod,
|
||||||
|
onRetrievalSearchMethodChange,
|
||||||
|
hybridSearchMode,
|
||||||
|
onHybridSearchModeChange,
|
||||||
|
weightedScore,
|
||||||
|
onWeightedScoreChange,
|
||||||
|
rerankingModel,
|
||||||
|
onRerankingModelChange,
|
||||||
|
}: 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,
|
||||||
|
])
|
||||||
|
|
||||||
const RetrievalSetting = () => {
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
fieldTitleProps={{
|
fieldTitleProps={{
|
||||||
@ -24,12 +154,10 @@ const RetrievalSetting = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className='space-y-1'>
|
<div className='space-y-1'>
|
||||||
<VectorSearchCard />
|
{options.map(renderOptionCard)}
|
||||||
<FullTextSearchCard />
|
|
||||||
<HybridSearchCard />
|
|
||||||
</div>
|
</div>
|
||||||
</Field>
|
</Field>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RetrievalSetting
|
export default memo(RetrievalSetting)
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
memo,
|
||||||
|
useMemo,
|
||||||
|
} from 'react'
|
||||||
|
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'
|
||||||
|
import type { RerankingModel } from '../../types'
|
||||||
|
|
||||||
|
type RerankingModelSelectorProps = {
|
||||||
|
rerankingModel?: RerankingModel
|
||||||
|
onRerankingModelChange?: (model: RerankingModel) => void
|
||||||
|
}
|
||||||
|
const RerankingModelSelector = ({
|
||||||
|
rerankingModel,
|
||||||
|
onRerankingModelChange,
|
||||||
|
}: RerankingModelSelectorProps) => {
|
||||||
|
const {
|
||||||
|
modelList: rerankModelList,
|
||||||
|
} = useModelListAndDefaultModel(ModelTypeEnum.rerank)
|
||||||
|
const rerankModel = useMemo(() => {
|
||||||
|
if (!rerankingModel)
|
||||||
|
return undefined
|
||||||
|
|
||||||
|
return {
|
||||||
|
provider_name: rerankingModel.reranking_provider_name,
|
||||||
|
model_name: rerankingModel.reranking_model_name,
|
||||||
|
}
|
||||||
|
}, [rerankingModel])
|
||||||
|
|
||||||
|
const handleRerankingModelChange = (model: DefaultModel) => {
|
||||||
|
onRerankingModelChange?.({
|
||||||
|
reranking_provider_name: model.provider,
|
||||||
|
reranking_model_name: model.model,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModelSelector
|
||||||
|
defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }}
|
||||||
|
modelList={rerankModelList}
|
||||||
|
onSelect={handleRerankingModelChange}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(RerankingModelSelector)
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { memo } from 'react'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import Input from '@/app/components/base/input'
|
||||||
|
import Switch from '@/app/components/base/switch'
|
||||||
|
|
||||||
|
const TopKAndScoreThreshold = () => {
|
||||||
|
return (
|
||||||
|
<div className='grid grid-cols-2 gap-4'>
|
||||||
|
<div>
|
||||||
|
<div className='system-xs-medium mb-0.5 flex h-6 items-center text-text-secondary'>
|
||||||
|
Top k
|
||||||
|
<Tooltip
|
||||||
|
triggerClassName='ml-0.5 shrink-0 w-3.5 h-3.5'
|
||||||
|
popupContent='top k'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className='mb-0.5 flex h-6 items-center'>
|
||||||
|
<Switch
|
||||||
|
className='mr-2'
|
||||||
|
/>
|
||||||
|
<div className='system-sm-medium grow truncate text-text-secondary'>
|
||||||
|
Score Threshold
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
triggerClassName='shrink-0 ml-0.5 w-3.5 h-3.5'
|
||||||
|
popupContent='Score Threshold'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
type='number'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(TopKAndScoreThreshold)
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import type { ComponentType } from 'react'
|
||||||
|
import type {
|
||||||
|
HybridSearchModeEnum,
|
||||||
|
RetrievalSearchMethodEnum,
|
||||||
|
} from '../../types'
|
||||||
|
|
||||||
|
export type Option = {
|
||||||
|
id: RetrievalSearchMethodEnum
|
||||||
|
icon: ComponentType<any>
|
||||||
|
title: any
|
||||||
|
description: string
|
||||||
|
effectColor?: string
|
||||||
|
showEffectColor?: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HybridSearchModeOption = {
|
||||||
|
id: HybridSearchModeEnum
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { VectorSearch } from '@/app/components/base/icons/src/vender/knowledge'
|
|
||||||
import OptionCard from '../option-card'
|
|
||||||
|
|
||||||
const VectorSearchCard = () => {
|
|
||||||
return (
|
|
||||||
<OptionCard
|
|
||||||
icon={<VectorSearch className='h-[15px] w-[15px] text-text-tertiary' />}
|
|
||||||
title='Vector Search'
|
|
||||||
description='Generate query embeddings and search for the text chunk most similar to its vector representation.'
|
|
||||||
effectColor='purple'
|
|
||||||
>
|
|
||||||
<div className='flex flex-col gap-2'>
|
|
||||||
<div>Vector Search Settings</div>
|
|
||||||
<div>Additional Settings</div>
|
|
||||||
</div>
|
|
||||||
</OptionCard>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VectorSearchCard
|
|
||||||
@ -1,5 +1,11 @@
|
|||||||
import type { NodeDefault } from '../../types'
|
import type { NodeDefault } from '../../types'
|
||||||
import type { KnowledgeBaseNodeType } from './types'
|
import type { KnowledgeBaseNodeType } from './types'
|
||||||
|
import {
|
||||||
|
ChunkStructureEnum,
|
||||||
|
HybridSearchModeEnum,
|
||||||
|
IndexMethodEnum,
|
||||||
|
RetrievalSearchMethodEnum,
|
||||||
|
} from './types'
|
||||||
import { genNodeMetaData } from '@/app/components/workflow/utils'
|
import { genNodeMetaData } from '@/app/components/workflow/utils'
|
||||||
import { BlockEnum } from '@/app/components/workflow/types'
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
|
||||||
@ -9,7 +15,19 @@ const metaData = genNodeMetaData({
|
|||||||
})
|
})
|
||||||
const nodeDefault: NodeDefault<KnowledgeBaseNodeType> = {
|
const nodeDefault: NodeDefault<KnowledgeBaseNodeType> = {
|
||||||
metaData,
|
metaData,
|
||||||
defaultValue: {},
|
defaultValue: {
|
||||||
|
index_chunk_variable_selector: [],
|
||||||
|
chunk_structure: ChunkStructureEnum.general,
|
||||||
|
indexing_technique: IndexMethodEnum.QUALIFIED,
|
||||||
|
keyword_number: 10,
|
||||||
|
retrieval_model: {
|
||||||
|
search_method: RetrievalSearchMethodEnum.hybrid,
|
||||||
|
top_k: 2,
|
||||||
|
score_threshold_enabled: false,
|
||||||
|
score_threshold: 0.5,
|
||||||
|
hybridSearchMode: HybridSearchModeEnum.WeightedScore,
|
||||||
|
},
|
||||||
|
},
|
||||||
checkValid() {
|
checkValid() {
|
||||||
return {
|
return {
|
||||||
isValid: true,
|
isValid: true,
|
||||||
|
|||||||
@ -1,19 +1,107 @@
|
|||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
useRef,
|
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
import { useStoreApi } from 'reactflow'
|
||||||
import type { KnowledgeBaseNodeType } from '../types'
|
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
||||||
|
import type {
|
||||||
|
ChunkStructureEnum,
|
||||||
|
HybridSearchModeEnum,
|
||||||
|
IndexMethodEnum,
|
||||||
|
KnowledgeBaseNodeType,
|
||||||
|
RerankingModel,
|
||||||
|
RetrievalSearchMethodEnum,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
export const useConfig = (id: string, payload: KnowledgeBaseNodeType) => {
|
export const useConfig = (id: string) => {
|
||||||
const {
|
const store = useStoreApi()
|
||||||
inputs,
|
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||||
setInputs,
|
|
||||||
} = useNodeCrud(id, payload)
|
|
||||||
const ref = useRef(inputs)
|
|
||||||
|
|
||||||
const handleInputsChange = useCallback((newInputs: KnowledgeBaseNodeType) => {
|
const getNodeData = useCallback(() => {
|
||||||
setInputs(newInputs)
|
const { getNodes } = store.getState()
|
||||||
ref.current = newInputs
|
const nodes = getNodes()
|
||||||
}, [setInputs, ref])
|
|
||||||
|
return nodes.find(node => node.id === id)
|
||||||
|
}, [store, id])
|
||||||
|
|
||||||
|
const handleNodeDataUpdate = useCallback((data: Partial<KnowledgeBaseNodeType>) => {
|
||||||
|
handleNodeDataUpdateWithSyncDraft({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}, [id, handleNodeDataUpdateWithSyncDraft])
|
||||||
|
|
||||||
|
const handleChunkStructureChange = useCallback((chunkStructure: ChunkStructureEnum) => {
|
||||||
|
handleNodeDataUpdate({ chunk_structure: chunkStructure })
|
||||||
|
}, [handleNodeDataUpdate])
|
||||||
|
|
||||||
|
const handleIndexMethodChange = useCallback((indexMethod: IndexMethodEnum) => {
|
||||||
|
handleNodeDataUpdate({ indexing_technique: indexMethod })
|
||||||
|
}, [handleNodeDataUpdate])
|
||||||
|
|
||||||
|
const handleKeywordNumberChange = useCallback((keywordNumber: number) => {
|
||||||
|
handleNodeDataUpdate({ keyword_number: keywordNumber })
|
||||||
|
}, [handleNodeDataUpdate])
|
||||||
|
|
||||||
|
const handleRetrievalSearchMethodChange = useCallback((searchMethod: RetrievalSearchMethodEnum) => {
|
||||||
|
const nodeData = getNodeData()
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
retrieval_model: {
|
||||||
|
...nodeData?.data.retrieval_model,
|
||||||
|
search_method: searchMethod,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [getNodeData, handleNodeDataUpdate])
|
||||||
|
|
||||||
|
const handleHybridSearchModeChange = useCallback((hybridSearchMode: HybridSearchModeEnum) => {
|
||||||
|
const nodeData = getNodeData()
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
retrieval_model: {
|
||||||
|
...nodeData?.data.retrieval_model,
|
||||||
|
hybridSearchMode,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [getNodeData, handleNodeDataUpdate])
|
||||||
|
|
||||||
|
const handleWeighedScoreChange = useCallback((weightedScore: { value: number[] }) => {
|
||||||
|
const nodeData = getNodeData()
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
retrieval_model: {
|
||||||
|
...nodeData?.data.retrieval_model,
|
||||||
|
weights: {
|
||||||
|
weight_type: 'weighted_score',
|
||||||
|
vector_setting: {
|
||||||
|
vector_weight: weightedScore.value[0],
|
||||||
|
embedding_provider_name: '',
|
||||||
|
embedding_model_name: '',
|
||||||
|
},
|
||||||
|
keyword_setting: {
|
||||||
|
keyword_weight: weightedScore.value[1],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [getNodeData, handleNodeDataUpdate])
|
||||||
|
|
||||||
|
const handleRerankingModelChange = useCallback((rerankingModel: RerankingModel) => {
|
||||||
|
const nodeData = getNodeData()
|
||||||
|
handleNodeDataUpdate({
|
||||||
|
retrieval_model: {
|
||||||
|
...nodeData?.data.retrieval_model,
|
||||||
|
reranking_model: {
|
||||||
|
reranking_provider_name: rerankingModel.reranking_provider_name,
|
||||||
|
reranking_model_name: rerankingModel.reranking_model_name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [getNodeData, handleNodeDataUpdate])
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleChunkStructureChange,
|
||||||
|
handleIndexMethodChange,
|
||||||
|
handleKeywordNumberChange,
|
||||||
|
handleRetrievalSearchMethodChange,
|
||||||
|
handleHybridSearchModeChange,
|
||||||
|
handleWeighedScoreChange,
|
||||||
|
handleRerankingModelChange,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { memo } from 'react'
|
import {
|
||||||
|
memo,
|
||||||
|
} from 'react'
|
||||||
import type { KnowledgeBaseNodeType } from './types'
|
import type { KnowledgeBaseNodeType } from './types'
|
||||||
|
import {
|
||||||
|
IndexMethodEnum,
|
||||||
|
} from './types'
|
||||||
import InputVariable from './components/input-variable'
|
import InputVariable from './components/input-variable'
|
||||||
import ChunkStructure from './components/chunk-structure'
|
import ChunkStructure from './components/chunk-structure'
|
||||||
import IndexMethod from './components/index-method'
|
import IndexMethod from './components/index-method'
|
||||||
import RetrievalSetting from './components/retrieval-setting'
|
import RetrievalSetting from './components/retrieval-setting'
|
||||||
import EmbeddingModel from './components/embedding-model'
|
import EmbeddingModel from './components/embedding-model'
|
||||||
|
import { useConfig } from './hooks/use-config'
|
||||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||||
import {
|
import {
|
||||||
Group,
|
Group,
|
||||||
@ -13,7 +19,20 @@ import {
|
|||||||
} from '@/app/components/workflow/nodes/_base/components/layout'
|
} from '@/app/components/workflow/nodes/_base/components/layout'
|
||||||
import Split from '../_base/components/split'
|
import Split from '../_base/components/split'
|
||||||
|
|
||||||
const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = () => {
|
const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = ({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
handleChunkStructureChange,
|
||||||
|
handleIndexMethodChange,
|
||||||
|
handleKeywordNumberChange,
|
||||||
|
handleRetrievalSearchMethodChange,
|
||||||
|
handleHybridSearchModeChange,
|
||||||
|
handleWeighedScoreChange,
|
||||||
|
handleRerankingModelChange,
|
||||||
|
} = useConfig(id)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<GroupWithBox boxProps={{ withBorderBottom: true }}>
|
<GroupWithBox boxProps={{ withBorderBottom: true }}>
|
||||||
@ -23,16 +42,37 @@ const Panel: FC<NodePanelProps<KnowledgeBaseNodeType>> = () => {
|
|||||||
className='py-3'
|
className='py-3'
|
||||||
withBorderBottom
|
withBorderBottom
|
||||||
>
|
>
|
||||||
<ChunkStructure />
|
<ChunkStructure
|
||||||
|
chunkStructure={data.chunk_structure}
|
||||||
|
onChunkStructureChange={handleChunkStructureChange}
|
||||||
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<GroupWithBox>
|
<GroupWithBox>
|
||||||
<div className='space-y-3'>
|
<div className='space-y-3'>
|
||||||
<IndexMethod />
|
<IndexMethod
|
||||||
<EmbeddingModel />
|
indexMethod={data.indexing_technique}
|
||||||
|
onIndexMethodChange={handleIndexMethodChange}
|
||||||
|
keywordNumber={data.keyword_number}
|
||||||
|
onKeywordNumberChange={handleKeywordNumberChange}
|
||||||
|
/>
|
||||||
|
{
|
||||||
|
data.indexing_technique === IndexMethodEnum.QUALIFIED && (
|
||||||
|
<EmbeddingModel />
|
||||||
|
)
|
||||||
|
}
|
||||||
<div className='pt-1'>
|
<div className='pt-1'>
|
||||||
<Split className='h-[1px]' />
|
<Split className='h-[1px]' />
|
||||||
</div>
|
</div>
|
||||||
<RetrievalSetting />
|
<RetrievalSetting
|
||||||
|
searchMethod={data.retrieval_model.search_method}
|
||||||
|
onRetrievalSearchMethodChange={handleRetrievalSearchMethodChange}
|
||||||
|
hybridSearchMode={data.retrieval_model.hybridSearchMode}
|
||||||
|
onHybridSearchModeChange={handleHybridSearchModeChange}
|
||||||
|
weightedScore={data.retrieval_model.weights}
|
||||||
|
onWeightedScoreChange={handleWeighedScoreChange}
|
||||||
|
rerankingModel={data.retrieval_model.reranking_model}
|
||||||
|
onRerankingModelChange={handleRerankingModelChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</GroupWithBox>
|
</GroupWithBox>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,52 @@
|
|||||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||||
|
import type { IndexingType } from '@/app/components/datasets/create/step-two'
|
||||||
|
import type { RETRIEVE_METHOD } from '@/types/app'
|
||||||
|
import type { WeightedScoreEnum } from '@/models/datasets'
|
||||||
|
import type { RerankingModeEnum } from '@/models/datasets'
|
||||||
|
export { WeightedScoreEnum } from '@/models/datasets'
|
||||||
|
export { IndexingType as IndexMethodEnum } from '@/app/components/datasets/create/step-two'
|
||||||
|
export { RETRIEVE_METHOD as RetrievalSearchMethodEnum } from '@/types/app'
|
||||||
|
export { RerankingModeEnum as HybridSearchModeEnum } from '@/models/datasets'
|
||||||
|
|
||||||
export type KnowledgeBaseNodeType = CommonNodeType
|
export enum ChunkStructureEnum {
|
||||||
|
general = 'general',
|
||||||
|
parent_child = 'parent-child',
|
||||||
|
question_answer = 'question-answer',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RerankingModel = {
|
||||||
|
reranking_provider_name: string
|
||||||
|
reranking_model_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WeightedScore = {
|
||||||
|
weight_type: WeightedScoreEnum
|
||||||
|
vector_setting: {
|
||||||
|
vector_weight: number
|
||||||
|
embedding_provider_name: string
|
||||||
|
embedding_model_name: string
|
||||||
|
}
|
||||||
|
keyword_setting: {
|
||||||
|
keyword_weight: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RetrievalSetting = {
|
||||||
|
search_method: RETRIEVE_METHOD
|
||||||
|
reranking_enable?: boolean
|
||||||
|
reranking_model?: RerankingModel
|
||||||
|
weights?: WeightedScore
|
||||||
|
top_k: number
|
||||||
|
score_threshold_enabled: boolean
|
||||||
|
score_threshold: number
|
||||||
|
hybridSearchMode: RerankingModeEnum
|
||||||
|
}
|
||||||
|
export type KnowledgeBaseNodeType = CommonNodeType & {
|
||||||
|
index_chunk_variable_selector: string[]
|
||||||
|
chunk_structure: ChunkStructureEnum
|
||||||
|
indexing_technique: IndexingType
|
||||||
|
embedding_model?: string
|
||||||
|
embedding_model_provider?: string
|
||||||
|
keyword_number: number
|
||||||
|
retrieval_model: RetrievalSetting
|
||||||
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export enum BlockEnum {
|
|||||||
LoopStart = 'loop-start',
|
LoopStart = 'loop-start',
|
||||||
LoopEnd = 'loop-end',
|
LoopEnd = 'loop-end',
|
||||||
DataSource = 'data-source',
|
DataSource = 'data-source',
|
||||||
KnowledgeBase = 'knowledge-base',
|
KnowledgeBase = 'knowledge-index',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ControlMode {
|
export enum ControlMode {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user