mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 04:22:28 +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 { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||
import { useBuildQueryVariableOptions } from '@/pages/agent/hooks/use-get-begin-query';
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import { Avatar as AntAvatar, Form, Select, Space } from 'antd';
|
||||
import { toLower } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RAGFlowAvatar } from './ragflow-avatar';
|
||||
import { FormControl, FormField, FormItem, FormLabel } from './ui/form';
|
||||
import { MultiSelect } from './ui/multi-select';
|
||||
@ -66,9 +70,13 @@ const KnowledgeBaseItem = ({
|
||||
|
||||
export default KnowledgeBaseItem;
|
||||
|
||||
export function KnowledgeBaseFormField() {
|
||||
export function KnowledgeBaseFormField({
|
||||
showVariable = false,
|
||||
}: {
|
||||
showVariable?: boolean;
|
||||
}) {
|
||||
const form = useFormContext();
|
||||
const { t } = useTranslate('chat');
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
||||
|
||||
@ -76,6 +84,8 @@ export function KnowledgeBaseFormField() {
|
||||
(x) => x.parser_id !== DocumentParserType.Tag,
|
||||
);
|
||||
|
||||
const nextOptions = useBuildQueryVariableOptions();
|
||||
|
||||
const knowledgeOptions = filteredKnowledgeList.map((x) => ({
|
||||
label: x.name,
|
||||
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 (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="kb_ids"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('knowledgeBases')}</FormLabel>
|
||||
<FormLabel>{t('chat.knowledgeBases')}</FormLabel>
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
options={knowledgeOptions}
|
||||
options={options}
|
||||
onValueChange={field.onChange}
|
||||
placeholder={t('knowledgeBasesMessage')}
|
||||
placeholder={t('chat.knowledgeBasesMessage')}
|
||||
variant="inverted"
|
||||
maxCount={100}
|
||||
defaultValue={field.value}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// https://github.com/sersavan/shadcn-multi-select-component
|
||||
// src/components/multi-select.tsx
|
||||
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
@ -29,6 +30,51 @@ import {
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
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.
|
||||
* 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.
|
||||
* Each option object has a label, value, and an optional icon.
|
||||
*/
|
||||
options: {
|
||||
/** 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 }>;
|
||||
}[];
|
||||
options: (MultiSelectGroupOptionType | MultiSelectOptionType)[];
|
||||
|
||||
/**
|
||||
* Callback function triggered when the selected values change.
|
||||
@ -144,6 +183,11 @@ export const MultiSelect = React.forwardRef<
|
||||
const [isPopoverOpen, setIsPopoverOpen] = 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 = (
|
||||
event: React.KeyboardEvent<HTMLInputElement>,
|
||||
) => {
|
||||
@ -181,10 +225,10 @@ export const MultiSelect = React.forwardRef<
|
||||
};
|
||||
|
||||
const toggleAll = () => {
|
||||
if (selectedValues.length === options.length) {
|
||||
if (selectedValues.length === flatOptions.length) {
|
||||
handleClear();
|
||||
} else {
|
||||
const allValues = options.map((option) => option.value);
|
||||
const allValues = flatOptions.map((option) => option.value);
|
||||
setSelectedValues(allValues);
|
||||
onValueChange(allValues);
|
||||
}
|
||||
@ -210,7 +254,7 @@ export const MultiSelect = React.forwardRef<
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<div className="flex flex-wrap items-center">
|
||||
{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;
|
||||
return (
|
||||
<Badge
|
||||
@ -304,7 +348,7 @@ export const MultiSelect = React.forwardRef<
|
||||
<div
|
||||
className={cn(
|
||||
'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'
|
||||
: 'opacity-50 [&_svg]:invisible',
|
||||
)}
|
||||
@ -313,32 +357,38 @@ export const MultiSelect = React.forwardRef<
|
||||
</div>
|
||||
<span>(Select All)</span>
|
||||
</CommandItem>
|
||||
{options.map((option) => {
|
||||
const isSelected = selectedValues.includes(option.value);
|
||||
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>
|
||||
);
|
||||
})}
|
||||
{!options.some((x) => 'options' in x) &&
|
||||
(options as unknown as MultiSelectOptionType[]).map(
|
||||
(option) => {
|
||||
const isSelected = selectedValues.includes(option.value);
|
||||
return (
|
||||
<MultiCommandItem
|
||||
option={option}
|
||||
key={option.value}
|
||||
isSelected={isSelected}
|
||||
toggleOption={toggleOption}
|
||||
></MultiCommandItem>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</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 />
|
||||
<CommandGroup>
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
@ -2,11 +2,11 @@ import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||
import { IRetrievalNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import { Flex } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query';
|
||||
import { CommonHandle } from './handle';
|
||||
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
|
||||
import styles from './index.less';
|
||||
@ -21,6 +21,7 @@ function InnerRetrievalNode({
|
||||
selected,
|
||||
}: NodeProps<IRetrievalNode>) {
|
||||
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
||||
console.log('🚀 ~ InnerRetrievalNode ~ knowledgeBaseIds:', knowledgeBaseIds);
|
||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
||||
const knowledgeBases = useMemo(() => {
|
||||
return knowledgeBaseIds.map((x) => {
|
||||
@ -33,6 +34,8 @@ function InnerRetrievalNode({
|
||||
});
|
||||
}, [knowledgeList, knowledgeBaseIds]);
|
||||
|
||||
const getLabel = useGetVariableLabelByValue(id);
|
||||
|
||||
return (
|
||||
<ToolBar selected={selected} id={id} label={data.label}>
|
||||
<NodeWrapper selected={selected}>
|
||||
@ -63,25 +66,27 @@ function InnerRetrievalNode({
|
||||
[styles.nodeHeader]: knowledgeBaseIds.length > 0,
|
||||
})}
|
||||
></NodeHeader>
|
||||
<Flex vertical gap={8}>
|
||||
{knowledgeBases.map((knowledge) => {
|
||||
<section className="flex flex-col gap-2">
|
||||
{knowledgeBaseIds.map((id) => {
|
||||
const item = knowledgeList.find((y) => id === y.id);
|
||||
const label = getLabel(id);
|
||||
|
||||
return (
|
||||
<div className={styles.nodeText} key={knowledge.id}>
|
||||
<Flex align={'center'} gap={6}>
|
||||
<div className={styles.nodeText} key={id}>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<RAGFlowAvatar
|
||||
className="size-6 rounded-lg"
|
||||
avatar={knowledge.avatar}
|
||||
name={knowledge.name || 'CN'}
|
||||
avatar={id}
|
||||
name={item?.name || (label as string) || 'CN'}
|
||||
isPerson={true}
|
||||
/>
|
||||
<Flex className={styles.knowledgeNodeName} flex={1}>
|
||||
{knowledge.name}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<div className={'truncate flex-1'}>{label || item?.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</section>
|
||||
</NodeWrapper>
|
||||
</ToolBar>
|
||||
);
|
||||
|
||||
@ -97,7 +97,7 @@ function RetrievalForm({ node }: INextOperatorForm) {
|
||||
<FormWrapper>
|
||||
<FormContainer>
|
||||
<QueryVariable></QueryVariable>
|
||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
||||
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
|
||||
</FormContainer>
|
||||
<Collapse title={<div>Advanced Settings</div>}>
|
||||
<FormContainer>
|
||||
|
||||
@ -43,7 +43,7 @@ const RetrievalForm = () => {
|
||||
>
|
||||
<FormContainer>
|
||||
<DescriptionField></DescriptionField>
|
||||
<KnowledgeBaseFormField></KnowledgeBaseFormField>
|
||||
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
|
||||
</FormContainer>
|
||||
<Collapse title={<div>Advanced Settings</div>}>
|
||||
<FormContainer>
|
||||
|
||||
@ -175,7 +175,7 @@ const ChatContainer = () => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div ref={ref} />
|
||||
<div ref={ref.scrollRef} />
|
||||
</div>
|
||||
<div className="flex w-full justify-center mb-8">
|
||||
<div className="w-5/6">
|
||||
|
||||
Reference in New Issue
Block a user