Feat: Files uploaded via the dialog box can be uploaded without binding to a dataset. #9590 (#11630)

### What problem does this PR solve?

Feat: Files uploaded via the dialog box can be uploaded without binding
to a dataset. #9590

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-12-01 16:29:02 +08:00
committed by GitHub
parent 221947acc4
commit 1120575021
11 changed files with 60 additions and 466 deletions

View File

@ -1,372 +0,0 @@
import { useTranslate } from '@/hooks/common-hooks';
import {
useDeleteDocument,
useFetchDocumentInfosByIds,
useRemoveNextDocument,
useUploadAndParseDocument,
} from '@/hooks/document-hooks';
import { cn } from '@/lib/utils';
import { getExtension } from '@/utils/document-util';
import { formatBytes } from '@/utils/file-util';
import {
CloseCircleOutlined,
InfoCircleOutlined,
LoadingOutlined,
} from '@ant-design/icons';
import type { GetProp, UploadFile } from 'antd';
import {
Button,
Card,
Divider,
Flex,
Input,
List,
Space,
Spin,
Typography,
Upload,
UploadProps,
} from 'antd';
import get from 'lodash/get';
import { CircleStop, Paperclip, SendHorizontal } from 'lucide-react';
import {
ChangeEventHandler,
memo,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import FileIcon from '../file-icon';
import styles from './index.less';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
const { Text } = Typography;
const { TextArea } = Input;
const getFileId = (file: UploadFile) => get(file, 'response.data.0');
const getFileIds = (fileList: UploadFile[]) => {
const ids = fileList.reduce((pre, cur) => {
return pre.concat(get(cur, 'response.data', []));
}, []);
return ids;
};
const isUploadSuccess = (file: UploadFile) => {
const code = get(file, 'response.code');
return typeof code === 'number' && code === 0;
};
interface IProps {
disabled: boolean;
value: string;
sendDisabled: boolean;
sendLoading: boolean;
onPressEnter(documentIds: string[]): void;
onInputChange: ChangeEventHandler<HTMLTextAreaElement>;
conversationId: string;
uploadMethod?: string;
isShared?: boolean;
showUploadIcon?: boolean;
createConversationBeforeUploadDocument?(message: string): Promise<any>;
stopOutputMessage?(): void;
}
const getBase64 = (file: FileType): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file as any);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
const MessageInput = ({
isShared = false,
disabled,
value,
onPressEnter,
sendDisabled,
sendLoading,
onInputChange,
conversationId,
showUploadIcon = true,
createConversationBeforeUploadDocument,
uploadMethod = 'upload_and_parse',
stopOutputMessage,
}: IProps) => {
const { t } = useTranslate('chat');
const { removeDocument } = useRemoveNextDocument();
const { deleteDocument } = useDeleteDocument();
const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds();
const { uploadAndParseDocument } = useUploadAndParseDocument(uploadMethod);
const conversationIdRef = useRef(conversationId);
const [fileList, setFileList] = useState<UploadFile[]>([]);
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType);
}
};
const handleChange: UploadProps['onChange'] = async ({
// fileList: newFileList,
file,
}) => {
let nextConversationId: string = conversationId;
if (createConversationBeforeUploadDocument) {
const creatingRet = await createConversationBeforeUploadDocument(
file.name,
);
if (creatingRet?.code === 0) {
nextConversationId = creatingRet.data.id;
}
}
setFileList((list) => {
list.push({
...file,
status: 'uploading',
originFileObj: file as any,
});
return [...list];
});
const ret = await uploadAndParseDocument({
conversationId: nextConversationId,
fileList: [file],
});
setFileList((list) => {
const nextList = list.filter((x) => x.uid !== file.uid);
nextList.push({
...file,
originFileObj: file as any,
response: ret,
percent: 100,
status: ret?.code === 0 ? 'done' : 'error',
});
return nextList;
});
};
const isUploadingFile = fileList.some((x) => x.status === 'uploading');
const handlePressEnter = useCallback(async () => {
if (isUploadingFile) return;
const ids = getFileIds(fileList.filter((x) => isUploadSuccess(x)));
onPressEnter(ids);
setFileList([]);
}, [fileList, onPressEnter, isUploadingFile]);
const handleKeyDown = useCallback(
async (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
// check if it was shift + enter
if (event.key === 'Enter' && event.shiftKey) return;
if (event.key !== 'Enter') return;
if (sendDisabled || isUploadingFile || sendLoading) return;
event.preventDefault();
handlePressEnter();
},
[sendDisabled, isUploadingFile, sendLoading, handlePressEnter],
);
const handleRemove = useCallback(
async (file: UploadFile) => {
const ids = get(file, 'response.data', []);
// Upload Successfully
if (Array.isArray(ids) && ids.length) {
if (isShared) {
await deleteDocument(ids);
} else {
await removeDocument(ids[0]);
}
setFileList((preList) => {
return preList.filter((x) => getFileId(x) !== ids[0]);
});
} else {
// Upload failed
setFileList((preList) => {
return preList.filter((x) => x.uid !== file.uid);
});
}
},
[removeDocument, deleteDocument, isShared],
);
const handleStopOutputMessage = useCallback(() => {
stopOutputMessage?.();
}, [stopOutputMessage]);
const getDocumentInfoById = useCallback(
(id: string) => {
return documentInfos.find((x) => x.id === id);
},
[documentInfos],
);
useEffect(() => {
const ids = getFileIds(fileList);
setDocumentIds(ids);
}, [fileList, setDocumentIds]);
useEffect(() => {
if (
conversationIdRef.current &&
conversationId !== conversationIdRef.current
) {
setFileList([]);
}
conversationIdRef.current = conversationId;
}, [conversationId, setFileList]);
return (
<Flex
gap={1}
vertical
className={cn(styles.messageInputWrapper, 'dark:bg-black')}
>
<TextArea
size="large"
placeholder={t('sendPlaceholder')}
value={value}
allowClear
disabled={disabled}
style={{
border: 'none',
boxShadow: 'none',
padding: '0px 10px',
marginTop: 10,
}}
autoSize={{ minRows: 2, maxRows: 10 }}
onKeyDown={handleKeyDown}
onChange={onInputChange}
/>
<Divider style={{ margin: '5px 30px 10px 0px' }} />
<Flex justify="space-between" align="center">
{fileList.length > 0 && (
<List
grid={{
gutter: 16,
xs: 1,
sm: 1,
md: 1,
lg: 1,
xl: 2,
xxl: 4,
}}
dataSource={fileList}
className={styles.listWrapper}
renderItem={(item) => {
const id = getFileId(item);
const documentInfo = getDocumentInfoById(id);
const fileExtension = getExtension(documentInfo?.name ?? '');
const fileName = item.originFileObj?.name ?? '';
return (
<List.Item>
<Card className={styles.documentCard}>
<Flex gap={10} align="center">
{item.status === 'uploading' ? (
<Spin
indicator={
<LoadingOutlined style={{ fontSize: 24 }} spin />
}
/>
) : item.status === 'error' ? (
<InfoCircleOutlined size={30}></InfoCircleOutlined>
) : (
<FileIcon id={id} name={fileName}></FileIcon>
)}
<Flex vertical style={{ width: '90%' }}>
<Text
ellipsis={{ tooltip: fileName }}
className={styles.nameText}
>
<b> {fileName}</b>
</Text>
{item.status === 'error' ? (
t('uploadFailed')
) : (
<>
{item.percent !== 100 ? (
t('uploading')
) : !item.response ? (
t('parsing')
) : (
<Space>
<span>{fileExtension?.toUpperCase()},</span>
<span>
{formatBytes(
getDocumentInfoById(id)?.size ?? 0,
)}
</span>
</Space>
)}
</>
)}
</Flex>
</Flex>
{item.status !== 'uploading' && (
<span className={styles.deleteIcon}>
<CloseCircleOutlined
onClick={() => handleRemove(item)}
/>
</span>
)}
</Card>
</List.Item>
);
}}
/>
)}
<Flex
gap={5}
align="center"
justify="flex-end"
style={{
paddingRight: 10,
paddingBottom: 10,
width: fileList.length > 0 ? '50%' : '100%',
}}
>
{showUploadIcon && (
<Upload
onPreview={handlePreview}
onChange={handleChange}
multiple={false}
onRemove={handleRemove}
showUploadList={false}
beforeUpload={() => {
return false;
}}
>
<Button type={'primary'} disabled={disabled}>
<Paperclip className="size-4" />
</Button>
</Upload>
)}
{sendLoading ? (
<Button onClick={handleStopOutputMessage}>
<CircleStop className="size-5" />
</Button>
) : (
<Button
type="primary"
onClick={handlePressEnter}
loading={sendLoading}
disabled={sendDisabled || isUploadingFile || sendLoading}
>
<SendHorizontal className="size-5" />
</Button>
)}
</Flex>
</Flex>
</Flex>
);
};
export default memo(MessageInput);

View File

@ -4,19 +4,16 @@ import {
IMessage, IMessage,
IReference, IReference,
IReferenceChunk, IReferenceChunk,
UploadResponseDataType,
} from '@/interfaces/database/chat'; } from '@/interfaces/database/chat';
import classNames from 'classnames'; import classNames from 'classnames';
import { memo, useCallback, useEffect, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import {
useFetchDocumentInfosByIds,
useFetchDocumentThumbnailsByIds,
} from '@/hooks/document-hooks';
import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks'; import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import MarkdownContent from '../markdown-content'; import MarkdownContent from '../markdown-content';
import { ReferenceDocumentList } from '../next-message-item/reference-document-list'; import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
import { InnerUploadedMessageFiles } from '../next-message-item/uploaded-message-files'; import { UploadedMessageFiles } from '../next-message-item/uploaded-message-files';
import { RAGFlowAvatar } from '../ragflow-avatar'; import { RAGFlowAvatar } from '../ragflow-avatar';
import { useTheme } from '../theme-provider'; import { useTheme } from '../theme-provider';
import { AssistantGroupButton, UserGroupButton } from './group-button'; import { AssistantGroupButton, UserGroupButton } from './group-button';
@ -55,9 +52,10 @@ const MessageItem = ({
const { theme } = useTheme(); const { theme } = useTheme();
const isAssistant = item.role === MessageType.Assistant; const isAssistant = item.role === MessageType.Assistant;
const isUser = item.role === MessageType.User; const isUser = item.role === MessageType.User;
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
const { data: documentThumbnails, setDocumentIds: setIds } = const uploadedFiles = useMemo(() => {
useFetchDocumentThumbnailsByIds(); return item?.files ?? [];
}, [item?.files]);
const referenceDocumentList = useMemo(() => { const referenceDocumentList = useMemo(() => {
return reference?.doc_aggs ?? []; return reference?.doc_aggs ?? [];
@ -67,17 +65,6 @@ const MessageItem = ({
regenerateMessage?.(item); regenerateMessage?.(item);
}, [regenerateMessage, item]); }, [regenerateMessage, item]);
useEffect(() => {
const ids = item?.doc_ids ?? [];
if (ids.length) {
setDocumentIds(ids);
const documentIds = ids.filter((x) => !(x in documentThumbnails));
if (documentIds.length) {
setIds(documentIds);
}
}
}, [item.doc_ids, setDocumentIds, setIds, documentThumbnails]);
return ( return (
<div <div
className={classNames(styles.messageItem, { className={classNames(styles.messageItem, {
@ -157,11 +144,13 @@ const MessageItem = ({
list={referenceDocumentList} list={referenceDocumentList}
></ReferenceDocumentList> ></ReferenceDocumentList>
)} )}
{isUser && documentList.length > 0 && ( {isUser &&
<InnerUploadedMessageFiles Array.isArray(uploadedFiles) &&
files={documentList} uploadedFiles.length > 0 && (
></InnerUploadedMessageFiles> <UploadedMessageFiles
)} files={uploadedFiles as UploadResponseDataType[]}
></UploadedMessageFiles>
)}
</section> </section>
</div> </div>
</section> </section>

View File

@ -1,13 +1,13 @@
import { UploadResponseDataType } from '@/interfaces/database/chat';
import { IDocumentInfo } from '@/interfaces/database/document'; import { IDocumentInfo } from '@/interfaces/database/document';
import { getExtension } from '@/utils/document-util'; import { getExtension } from '@/utils/document-util';
import { formatBytes } from '@/utils/file-util'; import { formatBytes } from '@/utils/file-util';
import { memo } from 'react'; import { memo } from 'react';
import FileIcon from '../file-icon'; import FileIcon from '../file-icon';
import NewDocumentLink from '../new-document-link';
import SvgIcon from '../svg-icon'; import SvgIcon from '../svg-icon';
interface IProps { interface IProps {
files?: File[] | IDocumentInfo[]; files?: File[] | IDocumentInfo[] | UploadResponseDataType[];
} }
type NameWidgetType = { type NameWidgetType = {
@ -15,16 +15,16 @@ type NameWidgetType = {
size: number; size: number;
id?: string; id?: string;
}; };
function NameWidget({ name, size, id }: NameWidgetType) { function NameWidget({ name, size }: NameWidgetType) {
return ( return (
<div className="text-xs max-w-20"> <div className="text-xs max-w-20">
{id ? ( {/* {id ? (
<NewDocumentLink documentId={id} documentName={name} prefix="document"> <NewDocumentLink documentId={id} documentName={name} prefix="document">
{name} {name}
</NewDocumentLink> </NewDocumentLink>
) : ( ) : (
<div className="truncate">{name}</div> )} */}
)} <div className="truncate">{name}</div>
<p className="text-text-secondary pt-1">{formatBytes(size)}</p> <p className="text-text-secondary pt-1">{formatBytes(size)}</p>
</div> </div>
); );

View File

@ -6,7 +6,6 @@ import {
IDocumentMetaRequestBody, IDocumentMetaRequestBody,
} from '@/interfaces/request/document'; } from '@/interfaces/request/document';
import i18n from '@/locales/config'; import i18n from '@/locales/config';
import chatService from '@/services/chat-service';
import kbService, { listDocument } from '@/services/knowledge-service'; import kbService, { listDocument } from '@/services/knowledge-service';
import api, { api_host } from '@/utils/api'; import api, { api_host } from '@/utils/api';
import { buildChunkHighlights } from '@/utils/document-util'; import { buildChunkHighlights } from '@/utils/document-util';
@ -428,39 +427,6 @@ export const useDeleteDocument = () => {
return { data, loading, deleteDocument: mutateAsync }; return { data, loading, deleteDocument: mutateAsync };
}; };
export const useUploadAndParseDocument = (uploadMethod: string) => {
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['uploadAndParseDocument'],
mutationFn: async ({
conversationId,
fileList,
}: {
conversationId: string;
fileList: UploadFile[];
}) => {
try {
const formData = new FormData();
formData.append('conversation_id', conversationId);
fileList.forEach((file: UploadFile) => {
formData.append('file', file as any);
});
if (uploadMethod === 'upload_and_parse') {
const data = await kbService.upload_and_parse(formData);
return data?.data;
}
const data = await chatService.uploadAndParseExternal(formData);
return data?.data;
} catch (error) {}
},
});
return { data, loading, uploadAndParseDocument: mutateAsync };
};
export const useParseDocument = () => { export const useParseDocument = () => {
const { const {
data, data,

View File

@ -11,6 +11,7 @@ import { IAskRequestBody } from '@/interfaces/request/chat';
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message'; import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
import { isConversationIdExist } from '@/pages/next-chats/utils'; import { isConversationIdExist } from '@/pages/next-chats/utils';
import chatService from '@/services/next-chat-service'; import chatService from '@/services/next-chat-service';
import api from '@/utils/api';
import { buildMessageListWithUuid, getConversationId } from '@/utils/chat'; import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks'; import { useDebounce } from 'ahooks';
@ -427,6 +428,7 @@ export function useUploadAndParseFile() {
const { data } = await chatService.uploadAndParse( const { data } = await chatService.uploadAndParse(
{ {
url: api.upload_and_parse(conversationId || id),
signal: controller.current.signal, signal: controller.current.signal,
data: formData, data: formData,
onUploadProgress: ({ progress }) => { onUploadProgress: ({ progress }) => {

View File

@ -96,7 +96,7 @@ export interface Message {
id?: string; id?: string;
audio_binary?: string; audio_binary?: string;
data?: any; data?: any;
files?: File[]; files?: (File | UploadResponseDataType)[];
chatBoxId?: string; chatBoxId?: string;
attachment?: IAttachment; attachment?: IAttachment;
} }
@ -192,3 +192,14 @@ export interface IMessage extends Message {
export interface IClientConversation extends IConversation { export interface IClientConversation extends IConversation {
message: IMessage[]; message: IMessage[];
} }
export interface UploadResponseDataType {
created_at: number;
created_by: string;
extension: string;
id: string;
mime_type: string;
name: string;
preview_url: null;
size: number;
}

View File

@ -86,7 +86,7 @@ export const useSendMessage = (controller: AbortController) => {
const { conversationId, isNew } = useGetChatSearchParams(); const { conversationId, isNew } = useGetChatSearchParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange(); const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { handleUploadFile, fileIds, clearFileIds, isUploading, removeFile } = const { handleUploadFile, isUploading, removeFile, files, clearFiles } =
useUploadFile(); useUploadFile();
const { send, answer, done } = useSendMessageWithSse( const { send, answer, done } = useSendMessageWithSse(
@ -208,7 +208,7 @@ export const useSendMessage = (controller: AbortController) => {
addNewestQuestion({ addNewestQuestion({
content: value, content: value,
doc_ids: fileIds, files: files,
id, id,
role: MessageType.User, role: MessageType.User,
}); });
@ -218,16 +218,16 @@ export const useSendMessage = (controller: AbortController) => {
id, id,
content: value.trim(), content: value.trim(),
role: MessageType.User, role: MessageType.User,
doc_ids: fileIds, files: files,
}); });
} }
clearFileIds(); clearFiles();
}, [ }, [
value, value,
addNewestQuestion, addNewestQuestion,
fileIds, files,
done, done,
clearFileIds, clearFiles,
setValue, setValue,
handleSendMessage, handleSendMessage,
]); ]);

View File

@ -29,7 +29,7 @@ export function useSendMultipleChatMessage(
api.completeConversation, api.completeConversation,
); );
const { handleUploadFile, fileIds, clearFileIds } = useUploadFile(); const { handleUploadFile, files, clearFiles } = useUploadFile();
const { setFormRef, getLLMConfigById, isLLMConfigEmpty } = const { setFormRef, getLLMConfigById, isLLMConfigEmpty } =
useBuildFormRefs(chatBoxIds); useBuildFormRefs(chatBoxIds);
@ -181,7 +181,7 @@ export function useSendMultipleChatMessage(
id, id,
role: MessageType.User, role: MessageType.User,
chatBoxId, chatBoxId,
doc_ids: fileIds, files,
}); });
} }
}); });
@ -195,22 +195,22 @@ export function useSendMultipleChatMessage(
id, id,
content: value.trim(), content: value.trim(),
role: MessageType.User, role: MessageType.User,
doc_ids: fileIds, files,
}, },
chatBoxId, chatBoxId,
}); });
} }
}); });
} }
clearFileIds(); clearFiles();
}, [ }, [
value, value,
chatBoxIds, chatBoxIds,
allDone, allDone,
clearFileIds, clearFiles,
isLLMConfigEmpty, isLLMConfigEmpty,
addNewestQuestion, addNewestQuestion,
fileIds, files,
setValue, setValue,
sendMessage, sendMessage,
]); ]);

View File

@ -4,8 +4,10 @@ import { useCallback, useState } from 'react';
export function useUploadFile() { export function useUploadFile() {
const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile(); const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile();
const [fileIds, setFileIds] = useState<string[]>([]); const [currentFiles, setCurrentFiles] = useState<Record<string, any>[]>([]);
const [fileMap, setFileMap] = useState<Map<File, string>>(new Map()); const [fileMap, setFileMap] = useState<Map<File, Record<string, any>>>(
new Map(),
);
type FileUploadParameters = Parameters< type FileUploadParameters = Parameters<
NonNullable<FileUploadProps['onUpload']> NonNullable<FileUploadProps['onUpload']>
@ -20,10 +22,11 @@ export function useUploadFile() {
if (Array.isArray(files) && files.length) { if (Array.isArray(files) && files.length) {
const file = files[0]; const file = files[0];
const ret = await uploadAndParseFile({ file, options, conversationId }); const ret = await uploadAndParseFile({ file, options, conversationId });
if (ret?.code === 0 && Array.isArray(ret?.data)) { if (ret?.code === 0) {
setFileIds((list) => [...list, ...ret.data]); const data = ret.data;
setCurrentFiles((list) => [...list, data]);
setFileMap((map) => { setFileMap((map) => {
map.set(files[0], ret.data[0]); map.set(files[0], data);
return map; return map;
}); });
} }
@ -32,8 +35,8 @@ export function useUploadFile() {
[uploadAndParseFile], [uploadAndParseFile],
); );
const clearFileIds = useCallback(() => { const clearFiles = useCallback(() => {
setFileIds([]); setCurrentFiles([]);
setFileMap(new Map()); setFileMap(new Map());
}, []); }, []);
@ -45,7 +48,7 @@ export function useUploadFile() {
} }
const id = fileMap.get(file); const id = fileMap.get(file);
if (id) { if (id) {
setFileIds((list) => list.filter((item) => item !== id)); setCurrentFiles((list) => list.filter((item) => item !== id));
} }
}, },
[cancel, fileMap, loading], [cancel, fileMap, loading],
@ -53,9 +56,9 @@ export function useUploadFile() {
return { return {
handleUploadFile, handleUploadFile,
clearFileIds, files: currentFiles,
fileIds,
isUploading: loading, isUploading: loading,
removeFile, removeFile,
clearFiles: clearFiles,
}; };
} }

View File

@ -35,7 +35,6 @@ const {
web_crawl, web_crawl,
knowledge_graph, knowledge_graph,
document_infos, document_infos,
upload_and_parse,
listTagByKnowledgeIds, listTagByKnowledgeIds,
setMeta, setMeta,
getMeta, getMeta,
@ -158,10 +157,6 @@ const methods = {
url: document_delete, url: document_delete,
method: 'delete', method: 'delete',
}, },
upload_and_parse: {
url: upload_and_parse,
method: 'post',
},
listTagByKnowledgeIds: { listTagByKnowledgeIds: {
url: listTagByKnowledgeIds, url: listTagByKnowledgeIds,
method: 'get', method: 'get',

View File

@ -107,7 +107,7 @@ export default {
document_upload: `${api_host}/document/upload`, document_upload: `${api_host}/document/upload`,
web_crawl: `${api_host}/document/web_crawl`, web_crawl: `${api_host}/document/web_crawl`,
document_infos: `${api_host}/document/infos`, document_infos: `${api_host}/document/infos`,
upload_and_parse: `${api_host}/document/upload_and_parse`, upload_and_parse: (id: string) => `${api_host}/document/upload_info`,
parse: `${api_host}/document/parse`, parse: `${api_host}/document/parse`,
setMeta: `${api_host}/document/set_meta`, setMeta: `${api_host}/document/set_meta`,
get_dataset_filter: `${api_host}/document/filter`, get_dataset_filter: `${api_host}/document/filter`,