Bläddra i källkod

Feat: Allow chat to use meta data #3221 (#9393)

### 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)
tags/v0.20.2
balibabu 2 månader sedan
förälder
incheckning
76118000c1
Inget konto är kopplat till bidragsgivarens mejladress

+ 8
- 8
web/.umirc.ts Visa fil

@@ -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;
},

+ 2
- 2
web/src/hooks/common-hooks.tsx Visa fil

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

+ 20
- 1
web/src/hooks/use-knowledge-request.ts Visa fil

@@ -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 };
}

+ 3
- 0
web/src/locales/en.ts Visa fil

@@ -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',

+ 38
- 0
web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx Visa fil

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

+ 84
- 0
web/src/pages/chat/chat-configuration-modal/metadata-filter-conditions.tsx Visa fil

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

+ 6
- 0
web/src/pages/chat/constants.ts Visa fil

@@ -1 +1,7 @@
export const EmptyConversationId = 'empty';

export enum DatasetMetadata {
Disabled = 'disabled',
Automatic = 'automatic',
Manual = 'manual',
}

+ 12
- 3
web/src/pages/next-chats/chat/app-settings/chat-settings.tsx Visa fil

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

+ 4
- 4
web/src/pages/next-chats/chat/chat-box.tsx Visa fil

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

+ 16
- 4
web/src/pages/next-chats/chat/index.tsx Visa fil

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

+ 41
- 9
web/src/pages/next-chats/chat/sessions.tsx Visa fil

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

+ 5
- 0
web/src/services/knowledge-service.ts Visa fil

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

+ 1
- 0
web/src/utils/api.ts Visa fil

@@ -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`,

Laddar…
Avbryt
Spara