mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: New Agent startup parameters add knowledge base parameter #9194 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,9 +1,13 @@
|
|||||||
import { DocumentParserType } from '@/constants/knowledge';
|
import { DocumentParserType } from '@/constants/knowledge';
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||||
|
import { useBuildQueryVariableOptions } from '@/pages/agent/hooks/use-get-begin-query';
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
import { UserOutlined } from '@ant-design/icons';
|
||||||
import { Avatar as AntAvatar, Form, Select, Space } from 'antd';
|
import { Avatar as AntAvatar, Form, Select, Space } from 'antd';
|
||||||
|
import { toLower } from 'lodash';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { RAGFlowAvatar } from './ragflow-avatar';
|
import { RAGFlowAvatar } from './ragflow-avatar';
|
||||||
import { FormControl, FormField, FormItem, FormLabel } from './ui/form';
|
import { FormControl, FormField, FormItem, FormLabel } from './ui/form';
|
||||||
import { MultiSelect } from './ui/multi-select';
|
import { MultiSelect } from './ui/multi-select';
|
||||||
@ -66,9 +70,13 @@ const KnowledgeBaseItem = ({
|
|||||||
|
|
||||||
export default KnowledgeBaseItem;
|
export default KnowledgeBaseItem;
|
||||||
|
|
||||||
export function KnowledgeBaseFormField() {
|
export function KnowledgeBaseFormField({
|
||||||
|
showVariable = false,
|
||||||
|
}: {
|
||||||
|
showVariable?: boolean;
|
||||||
|
}) {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const { t } = useTranslate('chat');
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
||||||
|
|
||||||
@ -76,6 +84,8 @@ export function KnowledgeBaseFormField() {
|
|||||||
(x) => x.parser_id !== DocumentParserType.Tag,
|
(x) => x.parser_id !== DocumentParserType.Tag,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const nextOptions = useBuildQueryVariableOptions();
|
||||||
|
|
||||||
const knowledgeOptions = filteredKnowledgeList.map((x) => ({
|
const knowledgeOptions = filteredKnowledgeList.map((x) => ({
|
||||||
label: x.name,
|
label: x.name,
|
||||||
value: x.id,
|
value: x.id,
|
||||||
@ -84,18 +94,48 @@ export function KnowledgeBaseFormField() {
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
if (showVariable) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('knowledgeDetails.dataset'),
|
||||||
|
options: knowledgeOptions,
|
||||||
|
},
|
||||||
|
...nextOptions.map((x) => {
|
||||||
|
return {
|
||||||
|
...x,
|
||||||
|
options: x.options
|
||||||
|
.filter((y) => toLower(y.type).includes('string'))
|
||||||
|
.map((x) => ({
|
||||||
|
...x,
|
||||||
|
icon: () => (
|
||||||
|
<RAGFlowAvatar
|
||||||
|
className="size-4 mr-2"
|
||||||
|
avatar={x.label}
|
||||||
|
name={x.label}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return knowledgeOptions;
|
||||||
|
}, [knowledgeOptions, nextOptions, showVariable, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="kb_ids"
|
name="kb_ids"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('knowledgeBases')}</FormLabel>
|
<FormLabel>{t('chat.knowledgeBases')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={knowledgeOptions}
|
options={options}
|
||||||
onValueChange={field.onChange}
|
onValueChange={field.onChange}
|
||||||
placeholder={t('knowledgeBasesMessage')}
|
placeholder={t('chat.knowledgeBasesMessage')}
|
||||||
variant="inverted"
|
variant="inverted"
|
||||||
maxCount={100}
|
maxCount={100}
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
// https://github.com/sersavan/shadcn-multi-select-component
|
||||||
// src/components/multi-select.tsx
|
// src/components/multi-select.tsx
|
||||||
|
|
||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
@ -29,6 +30,51 @@ import {
|
|||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export type MultiSelectOptionType = {
|
||||||
|
label: React.ReactNode;
|
||||||
|
value: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
icon?: React.ComponentType<{ className?: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MultiSelectGroupOptionType = {
|
||||||
|
label: React.ReactNode;
|
||||||
|
options: MultiSelectOptionType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function MultiCommandItem({
|
||||||
|
option,
|
||||||
|
isSelected,
|
||||||
|
toggleOption,
|
||||||
|
}: {
|
||||||
|
option: MultiSelectOptionType;
|
||||||
|
isSelected: boolean;
|
||||||
|
toggleOption(value: string): void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={option.value}
|
||||||
|
onSelect={() => toggleOption(option.value)}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
||||||
|
isSelected
|
||||||
|
? 'bg-primary text-primary-foreground'
|
||||||
|
: 'opacity-50 [&_svg]:invisible',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
{option.icon && (
|
||||||
|
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
<span>{option.label}</span>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Variants for the multi-select component to handle different styles.
|
* Variants for the multi-select component to handle different styles.
|
||||||
* Uses class-variance-authority (cva) to define different styles based on "variant" prop.
|
* Uses class-variance-authority (cva) to define different styles based on "variant" prop.
|
||||||
@ -63,14 +109,7 @@ interface MultiSelectProps
|
|||||||
* An array of option objects to be displayed in the multi-select component.
|
* An array of option objects to be displayed in the multi-select component.
|
||||||
* Each option object has a label, value, and an optional icon.
|
* Each option object has a label, value, and an optional icon.
|
||||||
*/
|
*/
|
||||||
options: {
|
options: (MultiSelectGroupOptionType | MultiSelectOptionType)[];
|
||||||
/** The text to display for the option. */
|
|
||||||
label: string;
|
|
||||||
/** The unique value associated with the option. */
|
|
||||||
value: string;
|
|
||||||
/** Optional icon component to display alongside the option. */
|
|
||||||
icon?: React.ComponentType<{ className?: string }>;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback function triggered when the selected values change.
|
* Callback function triggered when the selected values change.
|
||||||
@ -144,6 +183,11 @@ export const MultiSelect = React.forwardRef<
|
|||||||
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
||||||
const [isAnimating, setIsAnimating] = React.useState(false);
|
const [isAnimating, setIsAnimating] = React.useState(false);
|
||||||
|
|
||||||
|
const flatOptions = React.useMemo(() => {
|
||||||
|
return options.flatMap((option) =>
|
||||||
|
'options' in option ? option.options : [option],
|
||||||
|
);
|
||||||
|
}, [options]);
|
||||||
const handleInputKeyDown = (
|
const handleInputKeyDown = (
|
||||||
event: React.KeyboardEvent<HTMLInputElement>,
|
event: React.KeyboardEvent<HTMLInputElement>,
|
||||||
) => {
|
) => {
|
||||||
@ -181,10 +225,10 @@ export const MultiSelect = React.forwardRef<
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggleAll = () => {
|
const toggleAll = () => {
|
||||||
if (selectedValues.length === options.length) {
|
if (selectedValues.length === flatOptions.length) {
|
||||||
handleClear();
|
handleClear();
|
||||||
} else {
|
} else {
|
||||||
const allValues = options.map((option) => option.value);
|
const allValues = flatOptions.map((option) => option.value);
|
||||||
setSelectedValues(allValues);
|
setSelectedValues(allValues);
|
||||||
onValueChange(allValues);
|
onValueChange(allValues);
|
||||||
}
|
}
|
||||||
@ -210,7 +254,7 @@ export const MultiSelect = React.forwardRef<
|
|||||||
<div className="flex justify-between items-center w-full">
|
<div className="flex justify-between items-center w-full">
|
||||||
<div className="flex flex-wrap items-center">
|
<div className="flex flex-wrap items-center">
|
||||||
{selectedValues?.slice(0, maxCount)?.map((value) => {
|
{selectedValues?.slice(0, maxCount)?.map((value) => {
|
||||||
const option = options.find((o) => o.value === value);
|
const option = flatOptions.find((o) => o.value === value);
|
||||||
const IconComponent = option?.icon;
|
const IconComponent = option?.icon;
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
@ -304,7 +348,7 @@ export const MultiSelect = React.forwardRef<
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
||||||
selectedValues.length === options.length
|
selectedValues.length === flatOptions.length
|
||||||
? 'bg-primary text-primary-foreground'
|
? 'bg-primary text-primary-foreground'
|
||||||
: 'opacity-50 [&_svg]:invisible',
|
: 'opacity-50 [&_svg]:invisible',
|
||||||
)}
|
)}
|
||||||
@ -313,32 +357,38 @@ export const MultiSelect = React.forwardRef<
|
|||||||
</div>
|
</div>
|
||||||
<span>(Select All)</span>
|
<span>(Select All)</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
{options.map((option) => {
|
{!options.some((x) => 'options' in x) &&
|
||||||
const isSelected = selectedValues.includes(option.value);
|
(options as unknown as MultiSelectOptionType[]).map(
|
||||||
return (
|
(option) => {
|
||||||
<CommandItem
|
const isSelected = selectedValues.includes(option.value);
|
||||||
key={option.value}
|
return (
|
||||||
onSelect={() => toggleOption(option.value)}
|
<MultiCommandItem
|
||||||
className="cursor-pointer"
|
option={option}
|
||||||
>
|
key={option.value}
|
||||||
<div
|
isSelected={isSelected}
|
||||||
className={cn(
|
toggleOption={toggleOption}
|
||||||
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
></MultiCommandItem>
|
||||||
isSelected
|
);
|
||||||
? 'bg-primary text-primary-foreground'
|
},
|
||||||
: 'opacity-50 [&_svg]:invisible',
|
)}
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CheckIcon className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
{option.icon && (
|
|
||||||
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
|
||||||
)}
|
|
||||||
<span>{option.label}</span>
|
|
||||||
</CommandItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
|
{options.every((x) => 'options' in x) &&
|
||||||
|
options.map((x, idx) => (
|
||||||
|
<CommandGroup heading={x.label} key={idx}>
|
||||||
|
{x.options.map((option) => {
|
||||||
|
const isSelected = selectedValues.includes(option.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MultiCommandItem
|
||||||
|
option={option}
|
||||||
|
key={option.value}
|
||||||
|
isSelected={isSelected}
|
||||||
|
toggleOption={toggleOption}
|
||||||
|
></MultiCommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
))}
|
||||||
<CommandSeparator />
|
<CommandSeparator />
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
|
|||||||
@ -2,11 +2,11 @@ import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
|||||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||||
import { IRetrievalNode } from '@/interfaces/database/flow';
|
import { IRetrievalNode } from '@/interfaces/database/flow';
|
||||||
import { NodeProps, Position } from '@xyflow/react';
|
import { NodeProps, Position } from '@xyflow/react';
|
||||||
import { Flex } from 'antd';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { NodeHandleId } from '../../constant';
|
import { NodeHandleId } from '../../constant';
|
||||||
|
import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query';
|
||||||
import { CommonHandle } from './handle';
|
import { CommonHandle } from './handle';
|
||||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
@ -21,6 +21,7 @@ function InnerRetrievalNode({
|
|||||||
selected,
|
selected,
|
||||||
}: NodeProps<IRetrievalNode>) {
|
}: NodeProps<IRetrievalNode>) {
|
||||||
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
||||||
|
console.log('🚀 ~ InnerRetrievalNode ~ knowledgeBaseIds:', knowledgeBaseIds);
|
||||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
||||||
const knowledgeBases = useMemo(() => {
|
const knowledgeBases = useMemo(() => {
|
||||||
return knowledgeBaseIds.map((x) => {
|
return knowledgeBaseIds.map((x) => {
|
||||||
@ -33,6 +34,8 @@ function InnerRetrievalNode({
|
|||||||
});
|
});
|
||||||
}, [knowledgeList, knowledgeBaseIds]);
|
}, [knowledgeList, knowledgeBaseIds]);
|
||||||
|
|
||||||
|
const getLabel = useGetVariableLabelByValue(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolBar selected={selected} id={id} label={data.label}>
|
<ToolBar selected={selected} id={id} label={data.label}>
|
||||||
<NodeWrapper selected={selected}>
|
<NodeWrapper selected={selected}>
|
||||||
@ -63,25 +66,27 @@ function InnerRetrievalNode({
|
|||||||
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
|
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
|
||||||
})}
|
})}
|
||||||
></NodeHeader>
|
></NodeHeader>
|
||||||
<Flex vertical gap={8}>
|
<section className="flex flex-col gap-2">
|
||||||
{knowledgeBases.map((knowledge) => {
|
{knowledgeBaseIds.map((id) => {
|
||||||
|
const item = knowledgeList.find((y) => id === y.id);
|
||||||
|
const label = getLabel(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.nodeText} key={knowledge.id}>
|
<div className={styles.nodeText} key={id}>
|
||||||
<Flex align={'center'} gap={6}>
|
<div className="flex items-center gap-1.5">
|
||||||
<RAGFlowAvatar
|
<RAGFlowAvatar
|
||||||
className="size-6 rounded-lg"
|
className="size-6 rounded-lg"
|
||||||
avatar={knowledge.avatar}
|
avatar={id}
|
||||||
name={knowledge.name || 'CN'}
|
name={item?.name || (label as string) || 'CN'}
|
||||||
isPerson={true}
|
isPerson={true}
|
||||||
/>
|
/>
|
||||||
<Flex className={styles.knowledgeNodeName} flex={1}>
|
|
||||||
{knowledge.name}
|
<div className={'truncate flex-1'}>{label || item?.name}</div>
|
||||||
</Flex>
|
</div>
|
||||||
</Flex>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</section>
|
||||||
</NodeWrapper>
|
</NodeWrapper>
|
||||||
</ToolBar>
|
</ToolBar>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -97,7 +97,7 @@ function RetrievalForm({ node }: INextOperatorForm) {
|
|||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<QueryVariable></QueryVariable>
|
<QueryVariable></QueryVariable>
|
||||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
<Collapse title={<div>Advanced Settings</div>}>
|
<Collapse title={<div>Advanced Settings</div>}>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const RetrievalForm = () => {
|
|||||||
>
|
>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
<DescriptionField></DescriptionField>
|
<DescriptionField></DescriptionField>
|
||||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
<Collapse title={<div>Advanced Settings</div>}>
|
<Collapse title={<div>Advanced Settings</div>}>
|
||||||
<FormContainer>
|
<FormContainer>
|
||||||
|
|||||||
@ -175,7 +175,7 @@ const ChatContainer = () => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div ref={ref} />
|
<div ref={ref.scrollRef} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-center mb-8">
|
<div className="flex w-full justify-center mb-8">
|
||||||
<div className="w-5/6">
|
<div className="w-5/6">
|
||||||
|
|||||||
Reference in New Issue
Block a user