mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
### What problem does this PR solve? Feat: Allow chat to use meta data #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -53,14 +53,14 @@ export default defineConfig({
|
||||
|
||||
memo.optimization.minimizer('terser').use(TerserPlugin); // Fixed the issue that the page displayed an error after packaging lexical with terser
|
||||
|
||||
memo.plugin('eslint').use(ESLintPlugin, [
|
||||
{
|
||||
extensions: ['js', 'ts', 'tsx'],
|
||||
failOnError: true,
|
||||
exclude: ['**/node_modules/**', '**/mfsu**', '**/mfsu-virtual-entry**'],
|
||||
files: ['src/**/*.{js,ts,tsx}'],
|
||||
},
|
||||
]);
|
||||
// memo.plugin('eslint').use(ESLintPlugin, [
|
||||
// {
|
||||
// extensions: ['js', 'ts', 'tsx'],
|
||||
// failOnError: true,
|
||||
// exclude: ['**/node_modules/**', '**/mfsu**', '**/mfsu-virtual-entry**'],
|
||||
// files: ['src/**/*.{js,ts,tsx}'],
|
||||
// },
|
||||
// ]);
|
||||
|
||||
return memo;
|
||||
},
|
||||
|
||||
@ -4,8 +4,8 @@ import isEqual from 'lodash/isEqual';
|
||||
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useSetModalState = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
export const useSetModalState = (initialVisible = false) => {
|
||||
const [visible, setVisible] = useState(initialVisible);
|
||||
|
||||
const showModal = useCallback(() => {
|
||||
setVisible(true);
|
||||
|
||||
@ -28,6 +28,8 @@ export const enum KnowledgeApiAction {
|
||||
DeleteKnowledge = 'deleteKnowledge',
|
||||
SaveKnowledge = 'saveKnowledge',
|
||||
FetchKnowledgeDetail = 'fetchKnowledgeDetail',
|
||||
FetchKnowledgeGraph = 'fetchKnowledgeGraph',
|
||||
FetchMetadata = 'fetchMetadata',
|
||||
}
|
||||
|
||||
export const useKnowledgeBaseId = (): string => {
|
||||
@ -263,7 +265,7 @@ export function useFetchKnowledgeGraph() {
|
||||
const knowledgeBaseId = useKnowledgeBaseId();
|
||||
|
||||
const { data, isFetching: loading } = useQuery<IKnowledgeGraph>({
|
||||
queryKey: ['fetchKnowledgeGraph', knowledgeBaseId],
|
||||
queryKey: [KnowledgeApiAction.FetchKnowledgeGraph, knowledgeBaseId],
|
||||
initialData: { graph: {}, mind_map: {} } as IKnowledgeGraph,
|
||||
enabled: !!knowledgeBaseId,
|
||||
gcTime: 0,
|
||||
@ -275,3 +277,20 @@ export function useFetchKnowledgeGraph() {
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
export function useFetchKnowledgeMetadata(kbIds: string[] = []) {
|
||||
const { data, isFetching: loading } = useQuery<
|
||||
Record<string, Record<string, string[]>>
|
||||
>({
|
||||
queryKey: [KnowledgeApiAction.FetchMetadata, kbIds],
|
||||
initialData: {},
|
||||
enabled: kbIds.length > 0,
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await kbService.getMeta({ kb_ids: kbIds.join(',') });
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
@ -563,6 +563,9 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
crossLanguage: 'Cross-language search',
|
||||
crossLanguageTip: `Select one or more languages for cross‑language search. If no language is selected, the system searches with the original query.`,
|
||||
createChat: 'Create chat',
|
||||
metadata: 'Metadata',
|
||||
metadataTip: 'Metadata',
|
||||
conditions: 'Conditions',
|
||||
},
|
||||
setting: {
|
||||
profile: 'Profile',
|
||||
|
||||
@ -8,10 +8,25 @@ import classNames from 'classnames';
|
||||
import { useCallback } from 'react';
|
||||
import { ISegmentedContentProps } from '../interface';
|
||||
|
||||
import { DatasetMetadata } from '../constants';
|
||||
import styles from './index.less';
|
||||
import { MetadataFilterConditions } from './metadata-filter-conditions';
|
||||
|
||||
const emptyResponseField = ['prompt_config', 'empty_response'];
|
||||
|
||||
const MetadataOptions = Object.values(DatasetMetadata).map((x) => {
|
||||
let value: DatasetMetadata | boolean = x;
|
||||
if (x === DatasetMetadata.Disabled) {
|
||||
value = false;
|
||||
} else if (x === DatasetMetadata.Automatic) {
|
||||
value = true;
|
||||
}
|
||||
return {
|
||||
value,
|
||||
label: x,
|
||||
};
|
||||
});
|
||||
|
||||
const AssistantSetting = ({
|
||||
show,
|
||||
form,
|
||||
@ -20,6 +35,11 @@ const AssistantSetting = ({
|
||||
const { t } = useTranslate('chat');
|
||||
const { data } = useFetchTenantInfo(true);
|
||||
|
||||
const metadata = Form.useWatch(['meta_data_filter', 'auto'], form);
|
||||
const kbIds = Form.useWatch(['kb_ids'], form);
|
||||
|
||||
const hasKnowledge = Array.isArray(kbIds) && kbIds.length > 0;
|
||||
|
||||
const handleChange = useCallback(() => {
|
||||
const kbIds = form.getFieldValue('kb_ids');
|
||||
const emptyResponse = form.getFieldValue(emptyResponseField);
|
||||
@ -153,6 +173,24 @@ const AssistantSetting = ({
|
||||
required={false}
|
||||
onChange={handleChange}
|
||||
></KnowledgeBaseItem>
|
||||
{hasKnowledge && (
|
||||
<Form.Item
|
||||
label={t('metadata')}
|
||||
name={['meta_data_filter', 'auto']}
|
||||
tooltip={t('metadataTip')}
|
||||
>
|
||||
<Select options={MetadataOptions} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{hasKnowledge && metadata === DatasetMetadata.Manual && (
|
||||
<Form.Item
|
||||
label={t('conditions')}
|
||||
tooltip={t('ttsTip')}
|
||||
initialValue={false}
|
||||
>
|
||||
<MetadataFilterConditions kbIds={kbIds}></MetadataFilterConditions>
|
||||
</Form.Item>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request';
|
||||
import { SwitchOperatorOptions } from '@/pages/agent/constant';
|
||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Empty,
|
||||
Form,
|
||||
FormListOperation,
|
||||
Input,
|
||||
Select,
|
||||
Space,
|
||||
} from 'antd';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function MetadataFilterConditions({ kbIds }: { kbIds: string[] }) {
|
||||
const metadata = useFetchKnowledgeMetadata(kbIds);
|
||||
|
||||
const renderItems = useCallback(
|
||||
(add: FormListOperation['add']) => {
|
||||
if (Object.keys(metadata.data).length === 0) {
|
||||
return [{ key: 'noData', label: <Empty></Empty> }];
|
||||
}
|
||||
return Object.keys(metadata.data).map((key) => {
|
||||
return {
|
||||
key,
|
||||
onClick: () => {
|
||||
add({
|
||||
key,
|
||||
value: '',
|
||||
op: SwitchOperatorOptions[0].value,
|
||||
});
|
||||
},
|
||||
label: key,
|
||||
};
|
||||
});
|
||||
},
|
||||
[metadata],
|
||||
);
|
||||
return (
|
||||
<Form.List name={['meta_data_filter', 'manual']}>
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name, ...restField }) => (
|
||||
<Space
|
||||
key={key}
|
||||
style={{ display: 'flex', marginBottom: 8 }}
|
||||
align="baseline"
|
||||
>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'key']}
|
||||
rules={[{ required: true, message: 'Missing first name' }]}
|
||||
>
|
||||
<Input placeholder="First Name" />
|
||||
</Form.Item>
|
||||
<Form.Item {...restField} name={[name, 'op']} className="w-20">
|
||||
<Select
|
||||
options={SwitchOperatorOptions}
|
||||
popupMatchSelectWidth={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'value']}
|
||||
rules={[{ required: true, message: 'Missing last name' }]}
|
||||
>
|
||||
<Input placeholder="Last Name" />
|
||||
</Form.Item>
|
||||
<MinusCircleOutlined onClick={() => remove(name)} />
|
||||
</Space>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Dropdown trigger={['click']} menu={{ items: renderItems(add) }}>
|
||||
<Button type="dashed" block icon={<PlusOutlined />}>
|
||||
Add Condition
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
);
|
||||
}
|
||||
@ -1 +1,7 @@
|
||||
export const EmptyConversationId = 'empty';
|
||||
|
||||
export enum DatasetMetadata {
|
||||
Disabled = 'disabled',
|
||||
Automatic = 'automatic',
|
||||
Manual = 'manual',
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { PanelRightClose } from 'lucide-react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import ChatBasicSetting from './chat-basic-settings';
|
||||
@ -7,7 +8,8 @@ import { ChatModelSettings } from './chat-model-settings';
|
||||
import { ChatPromptEngine } from './chat-prompt-engine';
|
||||
import { useChatSettingSchema } from './use-chat-setting-schema';
|
||||
|
||||
export function ChatSettings() {
|
||||
type ChatSettingsProps = { switchSettingVisible(): void };
|
||||
export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
|
||||
const formSchema = useChatSettingSchema();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
@ -33,11 +35,18 @@ export function ChatSettings() {
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-6">
|
||||
<section className="p-5 w-[400px] max-w-[20%]">
|
||||
<div className="flex justify-between items-center text-base">
|
||||
Chat Settings
|
||||
<PanelRightClose
|
||||
className="size-4 cursor-pointer"
|
||||
onClick={switchSettingVisible}
|
||||
/>
|
||||
</div>
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6 overflow-auto max-h-[88vh] pr-4"
|
||||
className="space-y-6 overflow-auto max-h-[87vh] pr-4"
|
||||
>
|
||||
<ChatBasicSetting></ChatBasicSetting>
|
||||
<ChatPromptEngine></ChatPromptEngine>
|
||||
|
||||
@ -23,7 +23,7 @@ interface IProps {
|
||||
export function ChatBox({ controller }: IProps) {
|
||||
const {
|
||||
value,
|
||||
scrollRef,
|
||||
// scrollRef,
|
||||
messageContainerRef,
|
||||
sendLoading,
|
||||
derivedMessages,
|
||||
@ -43,8 +43,8 @@ export function ChatBox({ controller }: IProps) {
|
||||
const sendDisabled = useSendButtonDisabled(value);
|
||||
|
||||
return (
|
||||
<section className="border-x flex flex-col p-5 w-full">
|
||||
<div ref={messageContainerRef} className="flex-1 overflow-auto">
|
||||
<section className="border-x flex flex-col p-5 flex-1 min-w-0">
|
||||
<div ref={messageContainerRef} className="flex-1 overflow-auto min-h-0">
|
||||
<div className="w-full">
|
||||
{derivedMessages?.map((message, i) => {
|
||||
return (
|
||||
@ -75,7 +75,7 @@ export function ChatBox({ controller }: IProps) {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div ref={scrollRef} />
|
||||
{/* <div ref={scrollRef} /> */}
|
||||
</div>
|
||||
<NextMessageInput
|
||||
disabled={disabled}
|
||||
|
||||
@ -7,10 +7,12 @@ import {
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useFetchDialog } from '@/hooks/use-chat-request';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
||||
import { ChatSettings } from './app-settings/chat-settings';
|
||||
import { ChatBox } from './chat-box';
|
||||
import { Sessions } from './sessions';
|
||||
|
||||
@ -20,6 +22,8 @@ export default function Chat() {
|
||||
const { t } = useTranslation();
|
||||
const { handleConversationCardClick, controller } =
|
||||
useHandleClickConversationCard();
|
||||
const { visible: settingVisible, switchVisible: switchSettingVisible } =
|
||||
useSetModalState(true);
|
||||
|
||||
return (
|
||||
<section className="h-full flex flex-col">
|
||||
@ -39,10 +43,18 @@ export default function Chat() {
|
||||
</Breadcrumb>
|
||||
</PageHeader>
|
||||
<div className="flex flex-1 min-h-0">
|
||||
<Sessions
|
||||
handleConversationCardClick={handleConversationCardClick}
|
||||
></Sessions>
|
||||
<ChatBox controller={controller}></ChatBox>
|
||||
<div className="flex flex-1 min-w-0">
|
||||
<Sessions
|
||||
handleConversationCardClick={handleConversationCardClick}
|
||||
switchSettingVisible={switchSettingVisible}
|
||||
></Sessions>
|
||||
<ChatBox controller={controller}></ChatBox>
|
||||
</div>
|
||||
{settingVisible && (
|
||||
<ChatSettings
|
||||
switchSettingVisible={switchSettingVisible}
|
||||
></ChatSettings>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -1,21 +1,30 @@
|
||||
import { MoreButton } from '@/components/more-button';
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useGetChatSearchParams } from '@/hooks/use-chat-request';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import {
|
||||
useFetchDialog,
|
||||
useGetChatSearchParams,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { PanelLeftClose, PanelRightClose, Plus } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { useHandleClickConversationCard } from '../hooks/use-click-card';
|
||||
import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list';
|
||||
import { ChatSettingSheet } from './app-settings/chat-settings-sheet';
|
||||
|
||||
type SessionProps = Pick<
|
||||
ReturnType<typeof useHandleClickConversationCard>,
|
||||
'handleConversationCardClick'
|
||||
>;
|
||||
export function Sessions({ handleConversationCardClick }: SessionProps) {
|
||||
> & { switchSettingVisible(): void };
|
||||
export function Sessions({
|
||||
handleConversationCardClick,
|
||||
switchSettingVisible,
|
||||
}: SessionProps) {
|
||||
const { list: conversationList, addTemporaryConversation } =
|
||||
useSelectDerivedConversationList();
|
||||
const { data } = useFetchDialog();
|
||||
const { visible, switchVisible } = useSetModalState(true);
|
||||
|
||||
const handleCardClick = useCallback(
|
||||
(conversationId: string, isNew: boolean) => () => {
|
||||
@ -26,9 +35,32 @@ export function Sessions({ handleConversationCardClick }: SessionProps) {
|
||||
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
|
||||
if (!visible) {
|
||||
return (
|
||||
<PanelRightClose
|
||||
className="cursor-pointer size-4 mt-8"
|
||||
onClick={switchVisible}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="p-6 w-[400px] max-w-[20%] flex flex-col">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<section className="flex items-center text-base justify-between gap-2">
|
||||
<div className="flex gap-3 items-center min-w-0">
|
||||
<RAGFlowAvatar
|
||||
avatar={data.icon}
|
||||
name={data.name}
|
||||
className="size-8"
|
||||
></RAGFlowAvatar>
|
||||
<span className="flex-1 truncate">{data.name}</span>
|
||||
</div>
|
||||
<PanelLeftClose
|
||||
className="cursor-pointer size-4"
|
||||
onClick={switchVisible}
|
||||
/>
|
||||
</section>
|
||||
<div className="flex justify-between items-center mb-4 pt-10">
|
||||
<span className="text-xl font-bold">Conversations</span>
|
||||
<Button variant={'ghost'} onClick={addTemporaryConversation}>
|
||||
<Plus></Plus>
|
||||
@ -51,9 +83,9 @@ export function Sessions({ handleConversationCardClick }: SessionProps) {
|
||||
))}
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<ChatSettingSheet>
|
||||
<Button className="w-full">Chat Settings</Button>
|
||||
</ChatSettingSheet>
|
||||
<Button className="w-full" onClick={switchSettingVisible}>
|
||||
Chat Settings
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -37,6 +37,7 @@ const {
|
||||
upload_and_parse,
|
||||
listTagByKnowledgeIds,
|
||||
setMeta,
|
||||
getMeta,
|
||||
} = api;
|
||||
|
||||
const methods = {
|
||||
@ -159,6 +160,10 @@ const methods = {
|
||||
url: api.get_dataset_filter,
|
||||
method: 'post',
|
||||
},
|
||||
getMeta: {
|
||||
url: getMeta,
|
||||
method: 'get',
|
||||
},
|
||||
};
|
||||
|
||||
const kbService = registerServer<keyof typeof methods>(methods, request);
|
||||
|
||||
@ -44,6 +44,7 @@ export default {
|
||||
get_kb_detail: `${api_host}/kb/detail`,
|
||||
getKnowledgeGraph: (knowledgeId: string) =>
|
||||
`${api_host}/kb/${knowledgeId}/knowledge_graph`,
|
||||
getMeta: `${api_host}/kb/get_meta`,
|
||||
|
||||
// tags
|
||||
listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,
|
||||
|
||||
Reference in New Issue
Block a user