Parcourir la source

Feat: New Agent startup parameters add knowledge base parameter #9194 (#9210)

### 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)
tags/v0.20.1
balibabu il y a 2 mois
Parent
révision
26b85a10d1
Aucun compte lié à l'adresse e-mail de l'auteur

+ 45
- 5
web/src/components/knowledge-base-item.tsx Voir le fichier

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';


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);


(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,
), ),
})); }));


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}

+ 87
- 37
web/src/components/ui/multi-select.tsx Voir le fichier

// 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';
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.
* 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: {
/** 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. * Callback function triggered when the selected values change.
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>,
) => { ) => {
}; };


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);
} }
<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
<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',
)} )}
</div> </div>
<span>(Select All)</span> <span>(Select All)</span>
</CommandItem> </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> </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">

+ 17
- 12
web/src/pages/agent/canvas/node/retrieval-node.tsx Voir le fichier

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';
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) => {
}); });
}, [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}>
[styles.nodeHeader]: knowledgeBaseIds.length > 0, [styles.nodeHeader]: knowledgeBaseIds.length > 0,
})} })}
></NodeHeader> ></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 ( 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 <RAGFlowAvatar
className="size-6 rounded-lg" className="size-6 rounded-lg"
avatar={knowledge.avatar}
name={knowledge.name || 'CN'}
avatar={id}
name={item?.name || (label as string) || 'CN'}
isPerson={true} isPerson={true}
/> />
<Flex className={styles.knowledgeNodeName} flex={1}>
{knowledge.name}
</Flex>
</Flex>

<div className={'truncate flex-1'}>{label || item?.name}</div>
</div>
</div> </div>
); );
})} })}
</Flex>
</section>
</NodeWrapper> </NodeWrapper>
</ToolBar> </ToolBar>
); );

+ 1
- 1
web/src/pages/agent/form/retrieval-form/next.tsx Voir le fichier

<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>

+ 1
- 1
web/src/pages/agent/form/tool-form/retrieval-form/index.tsx Voir le fichier

> >
<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>

+ 1
- 1
web/src/pages/next-chats/share/index.tsx Voir le fichier

); );
})} })}
</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">

Chargement…
Annuler
Enregistrer