diff --git a/web/src/components/knowledge-base-item.tsx b/web/src/components/knowledge-base-item.tsx index 8d595c88d..b2ec7117e 100644 --- a/web/src/components/knowledge-base-item.tsx +++ b/web/src/components/knowledge-base-item.tsx @@ -70,6 +70,10 @@ const KnowledgeBaseItem = ({ export default KnowledgeBaseItem; +function buildQueryVariableOptionsByShowVariable(showVariable?: boolean) { + return showVariable ? useBuildQueryVariableOptions : () => []; +} + export function KnowledgeBaseFormField({ showVariable = false, }: { @@ -84,7 +88,7 @@ export function KnowledgeBaseFormField({ (x) => x.parser_id !== DocumentParserType.Tag, ); - const nextOptions = useBuildQueryVariableOptions(); + const nextOptions = buildQueryVariableOptionsByShowVariable(showVariable)(); const knowledgeOptions = filteredKnowledgeList.map((x) => ({ label: x.name, diff --git a/web/src/components/message-item/index.tsx b/web/src/components/message-item/index.tsx index 664f46afb..936eba59f 100644 --- a/web/src/components/message-item/index.tsx +++ b/web/src/components/message-item/index.tsx @@ -1,9 +1,8 @@ import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg'; import { MessageType } from '@/constants/chat'; -import { useSetModalState } from '@/hooks/common-hooks'; import { IReference, IReferenceChunk } from '@/interfaces/database/chat'; import classNames from 'classnames'; -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { memo, useCallback, useEffect, useMemo } from 'react'; import { useFetchDocumentInfosByIds, @@ -12,17 +11,13 @@ import { import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; import { IMessage } from '@/pages/chat/interface'; import MarkdownContent from '@/pages/chat/markdown-content'; -import { getExtension, isImage } from '@/utils/document-util'; -import { Avatar, Button, Flex, List, Space, Typography } from 'antd'; -import FileIcon from '../file-icon'; -import IndentedTreeModal from '../indented-tree/modal'; -import NewDocumentLink from '../new-document-link'; +import { Avatar, Flex, Space } from 'antd'; +import { ReferenceDocumentList } from '../next-message-item/reference-document-list'; +import { InnerUploadedMessageFiles } from '../next-message-item/uploaded-message-files'; import { useTheme } from '../theme-provider'; import { AssistantGroupButton, UserGroupButton } from './group-button'; import styles from './index.less'; -const { Text } = Typography; - interface IProps extends Partial, IRegenerateMessage { item: IMessage; reference: IReference; @@ -59,21 +54,11 @@ const MessageItem = ({ const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds(); const { data: documentThumbnails, setDocumentIds: setIds } = useFetchDocumentThumbnailsByIds(); - const { visible, hideModal, showModal } = useSetModalState(); - const [clickedDocumentId, setClickedDocumentId] = useState(''); const referenceDocumentList = useMemo(() => { return reference?.doc_aggs ?? []; }, [reference?.doc_aggs]); - const handleUserDocumentClick = useCallback( - (id: string) => () => { - setClickedDocumentId(id); - showModal(); - }, - [showModal], - ); - const handleRegenerateMessage = useCallback(() => { regenerateMessage?.(item); }, [regenerateMessage, item]); @@ -160,83 +145,18 @@ const MessageItem = ({ > {isAssistant && referenceDocumentList.length > 0 && ( - { - return ( - - - - - - {item.doc_name} - - - - ); - }} - /> + )} {isUser && documentList.length > 0 && ( - { - // TODO: - // const fileThumbnail = - // documentThumbnails[item.id] || documentThumbnails[item.id]; - const fileExtension = getExtension(item.name); - return ( - - - - - {isImage(fileExtension) ? ( - - {item.name} - - ) : ( - - )} - - - ); - }} - /> + )} - {visible && ( - - )} ); }; diff --git a/web/src/components/next-message-item/group-button.tsx b/web/src/components/next-message-item/group-button.tsx index 8a6875edd..810f09429 100644 --- a/web/src/components/next-message-item/group-button.tsx +++ b/web/src/components/next-message-item/group-button.tsx @@ -116,64 +116,6 @@ export const AssistantGroupButton = ({ )} ); - - return ( - <> - - - - - {showLoudspeaker && ( - - - {isPlaying ? : } - - - - )} - {showLikeButton && ( - <> - - - - - - - - )} - {prompt && ( - - - - )} - { - e.preventDefault(); - e.stopPropagation(); - handleShowLogSheet(); - }} - > - - - - {visible && ( - - )} - {promptVisible && ( - - )} - - ); }; interface UserGroupButtonProps extends Partial { diff --git a/web/src/components/next-message-item/index.tsx b/web/src/components/next-message-item/index.tsx index 27c9583d1..5dd6cdf60 100644 --- a/web/src/components/next-message-item/index.tsx +++ b/web/src/components/next-message-item/index.tsx @@ -1,6 +1,5 @@ import { ReactComponent as AssistantIcon } from '@/assets/svg/assistant.svg'; import { MessageType } from '@/constants/chat'; -import { useSetModalState } from '@/hooks/common-hooks'; import { IReferenceChunk, IReferenceObject } from '@/interfaces/database/chat'; import classNames from 'classnames'; import { @@ -21,7 +20,6 @@ import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workflow-timeline'; import { IMessage } from '@/pages/chat/interface'; import { isEmpty } from 'lodash'; import { Atom, ChevronDown, ChevronUp } from 'lucide-react'; -import IndentedTreeModal from '../indented-tree/modal'; import MarkdownContent from '../next-markdown-content'; import { RAGFlowAvatar } from '../ragflow-avatar'; import { useTheme } from '../theme-provider'; @@ -79,8 +77,6 @@ function MessageItem({ const { theme } = useTheme(); const isAssistant = item.role === MessageType.Assistant; const isUser = item.role === MessageType.User; - const { visible, hideModal } = useSetModalState(); - const [clickedDocumentId] = useState(''); const [showThinking, setShowThinking] = useState(false); const { setLastSendLoadingFunc } = useContext(AgentChatContext); @@ -200,8 +196,6 @@ function MessageItem({ sendLoading={sendLoading} > )} - - {/* {isAssistant ? '' : nickname} */} @@ -254,13 +248,6 @@ function MessageItem({ - {visible && ( - - )} ); } diff --git a/web/src/components/next-message-item/reference-document-list.tsx b/web/src/components/next-message-item/reference-document-list.tsx index 2fa42e34e..f2a54bdd0 100644 --- a/web/src/components/next-message-item/reference-document-list.tsx +++ b/web/src/components/next-message-item/reference-document-list.tsx @@ -8,7 +8,7 @@ export function ReferenceDocumentList({ list }: { list: Docagg[] }) {
{list.map((item) => ( - + + {id ? ( + + {name} + + ) : ( +
{name}
+ )} +

{formatBytes(size)}

+ + ); } export function InnerUploadedMessageFiles({ files = [] }: IProps) { return (
- {files?.map((file, idx) => ( -
- {file.type.startsWith('image/') ? ( - {file.name} - ) : ( - - )} -
-
{file.name}
-

{formatBytes(file.size)}

+ {files?.map((file, idx) => { + const name = file.name; + const isFile = file instanceof File; + + return ( +
+ {!isFile ? ( + + ) : file.type.startsWith('image/') ? ( + {name} + ) : ( + + )} +
-
- ))} + ); + })}
); } diff --git a/web/src/interfaces/database/chat.ts b/web/src/interfaces/database/chat.ts index 08e7c27a7..6e5f15740 100644 --- a/web/src/interfaces/database/chat.ts +++ b/web/src/interfaces/database/chat.ts @@ -57,6 +57,18 @@ export interface IDialog { similarity_threshold: number; top_k: number; top_n: number; + meta_data_filter: MetaDataFilter; +} + +interface MetaDataFilter { + manual: Manual[]; + method: string; +} + +interface Manual { + key: string; + op: string; + value: string; } export interface IConversation { diff --git a/web/src/pages/next-chats/chat/app-settings/chat-basic-settings.tsx b/web/src/pages/next-chats/chat/app-settings/chat-basic-settings.tsx index 3c81c384a..6aa9177c4 100644 --- a/web/src/pages/next-chats/chat/app-settings/chat-basic-settings.tsx +++ b/web/src/pages/next-chats/chat/app-settings/chat-basic-settings.tsx @@ -2,6 +2,8 @@ import { FileUploader } from '@/components/file-uploader'; import { KnowledgeBaseFormField } from '@/components/knowledge-base-item'; +import { SelectWithSearch } from '@/components/originui/select-with-search'; +import { RAGFlowFormItem } from '@/components/ragflow-form'; import { SwitchFormField } from '@/components/switch-fom-field'; import { TavilyFormField } from '@/components/tavily-form-field'; import { @@ -14,11 +16,26 @@ import { import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { useTranslate } from '@/hooks/common-hooks'; -import { useFormContext } from 'react-hook-form'; +import { useFormContext, useWatch } from 'react-hook-form'; +import { DatasetMetadata } from '../../constants'; +import { MetadataFilterConditions } from './metadata-filter-conditions'; export default function ChatBasicSetting() { const { t } = useTranslate('chat'); const form = useFormContext(); + const kbIds: string[] = useWatch({ control: form.control, name: 'kb_ids' }); + const metadata = useWatch({ + control: form.control, + name: 'meta_data_filter.method', + }); + const hasKnowledge = Array.isArray(kbIds) && kbIds.length > 0; + + const MetadataOptions = Object.values(DatasetMetadata).map((x) => { + return { + value: x, + label: t(`meta.${x}`), + }; + }); return (
@@ -108,6 +125,18 @@ export default function ChatBasicSetting() { > + {hasKnowledge && ( + + + + )} + {hasKnowledge && metadata === DatasetMetadata.Manual && ( + + )}
); } diff --git a/web/src/pages/next-chats/chat/app-settings/chat-settings.tsx b/web/src/pages/next-chats/chat/app-settings/chat-settings.tsx index 8196637f3..ad076e452 100644 --- a/web/src/pages/next-chats/chat/app-settings/chat-settings.tsx +++ b/web/src/pages/next-chats/chat/app-settings/chat-settings.tsx @@ -9,6 +9,7 @@ import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { useParams } from 'umi'; import { z } from 'zod'; +import { DatasetMetadata } from '../../constants'; import ChatBasicSetting from './chat-basic-settings'; import { ChatModelSettings } from './chat-model-settings'; import { ChatPromptEngine } from './chat-prompt-engine'; @@ -38,6 +39,10 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) { top_n: 8, vector_similarity_weight: 0.2, top_k: 1024, + meta_data_filter: { + method: DatasetMetadata.Disabled, + manual: [], + }, }, }); diff --git a/web/src/pages/next-chats/chat/app-settings/metadata-filter-conditions.tsx b/web/src/pages/next-chats/chat/app-settings/metadata-filter-conditions.tsx new file mode 100644 index 000000000..62eb2dbe5 --- /dev/null +++ b/web/src/pages/next-chats/chat/app-settings/metadata-filter-conditions.tsx @@ -0,0 +1,129 @@ +import { SelectWithSearch } from '@/components/originui/select-with-search'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Separator } from '@/components/ui/separator'; +import { useFetchKnowledgeMetadata } from '@/hooks/use-knowledge-request'; +import { SwitchOperatorOptions } from '@/pages/agent/constant'; +import { useBuildSwitchOperatorOptions } from '@/pages/agent/form/switch-form'; +import { Plus, X } from 'lucide-react'; +import { useCallback } from 'react'; +import { useFieldArray, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; + +export function MetadataFilterConditions({ kbIds }: { kbIds: string[] }) { + const { t } = useTranslation(); + const form = useFormContext(); + const name = 'meta_data_filter.manual'; + const metadata = useFetchKnowledgeMetadata(kbIds); + + const switchOperatorOptions = useBuildSwitchOperatorOptions(); + + const { fields, remove, append } = useFieldArray({ + name, + control: form.control, + }); + + const add = useCallback( + (key: string) => () => { + append({ + key, + value: '', + op: SwitchOperatorOptions[0].value, + }); + }, + [append], + ); + + return ( +
+
+ {t('chat.conditions')} + + + + + + {Object.keys(metadata.data).map((key, idx) => { + return ( + + {key} + + ); + })} + + +
+
+ {fields.map((field, index) => { + const typeField = `${name}.${index}.key`; + return ( +
+ ( + + + + + + + )} + /> + + ( + + + + + + + )} + /> + + ( + + + + + + + )} + /> + +
+ ); + })} +
+
+ ); +} diff --git a/web/src/pages/next-chats/chat/app-settings/use-chat-setting-schema.tsx b/web/src/pages/next-chats/chat/app-settings/use-chat-setting-schema.tsx index b68b4ee72..053dd1472 100644 --- a/web/src/pages/next-chats/chat/app-settings/use-chat-setting-schema.tsx +++ b/web/src/pages/next-chats/chat/app-settings/use-chat-setting-schema.tsx @@ -43,6 +43,18 @@ export function useChatSettingSchema() { llm_id: z.string().optional(), ...vectorSimilarityWeightSchema, ...topnSchema, + meta_data_filter: z + .object({ + method: z.string().optional(), + manual: z.array( + z.object({ + key: z.string(), + op: z.string(), + value: z.string(), + }), + ), + }) + .optional(), }); return formSchema; diff --git a/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx b/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx index 6a8cc01ea..6b0a10b28 100644 --- a/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx +++ b/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx @@ -27,6 +27,7 @@ export function SingleChatBox({ controller }: IProps) { messageContainerRef, sendLoading, derivedMessages, + isUploading, handleInputChange, handlePressEnter, regenerateMessage, @@ -91,6 +92,7 @@ export function SingleChatBox({ controller }: IProps) { } stopOutputMessage={stopOutputMessage} onUpload={handleUploadFile} + isUploading={isUploading} />
); diff --git a/web/src/pages/next-chats/constants.ts b/web/src/pages/next-chats/constants.ts new file mode 100644 index 000000000..ed0dcb514 --- /dev/null +++ b/web/src/pages/next-chats/constants.ts @@ -0,0 +1,7 @@ +export const EmptyConversationId = 'empty'; + +export enum DatasetMetadata { + Disabled = 'disabled', + Automatic = 'automatic', + Manual = 'manual', +} diff --git a/web/src/pages/next-chats/hooks/use-send-chat-message.ts b/web/src/pages/next-chats/hooks/use-send-chat-message.ts index 390743049..0a1483bb5 100644 --- a/web/src/pages/next-chats/hooks/use-send-chat-message.ts +++ b/web/src/pages/next-chats/hooks/use-send-chat-message.ts @@ -138,7 +138,8 @@ export const useSendMessage = (controller: AbortController) => { const { conversationId, isNew } = useGetChatSearchParams(); const { handleInputChange, value, setValue } = useHandleMessageInputChange(); - const { handleUploadFile, fileIds, clearFileIds } = useUploadFile(); + const { handleUploadFile, fileIds, clearFileIds, isUploading } = + useUploadFile(); const { send, answer, done } = useSendMessageWithSse( api.completeConversation, @@ -285,5 +286,6 @@ export const useSendMessage = (controller: AbortController) => { removeMessageById, stopOutputMessage, handleUploadFile, + isUploading, }; }; diff --git a/web/src/pages/next-chats/hooks/use-upload-file.ts b/web/src/pages/next-chats/hooks/use-upload-file.ts index 0bcc8d737..38e260ebe 100644 --- a/web/src/pages/next-chats/hooks/use-upload-file.ts +++ b/web/src/pages/next-chats/hooks/use-upload-file.ts @@ -3,7 +3,7 @@ import { useUploadAndParseFile } from '@/hooks/use-chat-request'; import { useCallback, useState } from 'react'; export function useUploadFile() { - const { uploadAndParseFile } = useUploadAndParseFile(); + const { uploadAndParseFile, loading } = useUploadAndParseFile(); const [fileIds, setFileIds] = useState([]); const handleUploadFile: NonNullable = @@ -23,5 +23,5 @@ export function useUploadFile() { setFileIds([]); }, []); - return { handleUploadFile, clearFileIds, fileIds }; + return { handleUploadFile, clearFileIds, fileIds, isUploading: loading }; }