mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? feat(search): Added app embedding functionality and optimized search page #3221 - Added an Embed App button and related functionality - Optimized the layout and interaction of the search settings interface - Adjusted the search result display method - Refactored some code to support new features ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@ -22,6 +22,7 @@ export const variableEnabledFieldMap = {
|
|||||||
export enum SharedFrom {
|
export enum SharedFrom {
|
||||||
Agent = 'agent',
|
Agent = 'agent',
|
||||||
Chat = 'chat',
|
Chat = 'chat',
|
||||||
|
Search = 'search',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ChatSearchParams {
|
export enum ChatSearchParams {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { useDebounce } from 'ahooks';
|
|||||||
import { get, set } from 'lodash';
|
import { get, set } from 'lodash';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import {
|
import {
|
||||||
useGetPaginationWithRouter,
|
useGetPaginationWithRouter,
|
||||||
@ -304,6 +304,9 @@ export const useSetAgent = (showMessage: boolean = true) => {
|
|||||||
// Only one file can be uploaded at a time
|
// Only one file can be uploaded at a time
|
||||||
export const useUploadCanvasFile = () => {
|
export const useUploadCanvasFile = () => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const shared_id = searchParams.get('shared_id');
|
||||||
|
const canvasId = id || shared_id;
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isPending: loading,
|
isPending: loading,
|
||||||
@ -321,7 +324,7 @@ export const useUploadCanvasFile = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await agentService.uploadCanvasFile(
|
const { data } = await agentService.uploadCanvasFile(
|
||||||
{ url: api.uploadAgentFile(id), data: nextBody },
|
{ url: api.uploadAgentFile(canvasId as string), data: nextBody },
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
if (data?.code === 0) {
|
if (data?.code === 0) {
|
||||||
|
|||||||
@ -1418,6 +1418,7 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
createSearch: 'Create Search',
|
createSearch: 'Create Search',
|
||||||
|
searchGreeting: 'How can I help you today ?',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1185,9 +1185,13 @@ export default {
|
|||||||
knowledge: '知識',
|
knowledge: '知識',
|
||||||
chat: '聊天',
|
chat: '聊天',
|
||||||
},
|
},
|
||||||
},
|
modal: {
|
||||||
modal: {
|
okText: '確認',
|
||||||
okText: '確認',
|
cancelText: '取消',
|
||||||
cancelText: '取消',
|
},
|
||||||
|
search: {
|
||||||
|
createSearch: '新建查詢',
|
||||||
|
searchGreeting: '今天我能為你做些什麽?',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1316,12 +1316,13 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
modal: {
|
||||||
modal: {
|
okText: '确认',
|
||||||
okText: '确认',
|
cancelText: '取消',
|
||||||
cancelText: '取消',
|
},
|
||||||
},
|
search: {
|
||||||
search: {
|
createSearch: '新建查询',
|
||||||
createSearch: '新建查询',
|
searchGreeting: '今天我能为你做些什么?',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -85,7 +85,7 @@ const getUrlWithToken = (token: string, from: string = 'chat') => {
|
|||||||
return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`;
|
return `${protocol}//${host}/chat/share?shared_id=${token}&from=${from}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useFetchTokenListBeforeOtherStep = () => {
|
export const useFetchTokenListBeforeOtherStep = () => {
|
||||||
const { showTokenEmptyError } = useShowTokenEmptyError();
|
const { showTokenEmptyError } = useShowTokenEmptyError();
|
||||||
const { showBetaEmptyError } = useShowBetaEmptyError();
|
const { showBetaEmptyError } = useShowBetaEmptyError();
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,8 @@ import { useEffect, useState } from 'react';
|
|||||||
|
|
||||||
interface IProps extends IModalProps<any> {
|
interface IProps extends IModalProps<any> {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
chunk: IChunk | IReferenceChunk;
|
chunk: IChunk &
|
||||||
|
IReferenceChunk & { docnm_kwd: string; document_name: string };
|
||||||
}
|
}
|
||||||
function getFileExtensionRegex(filename: string): string {
|
function getFileExtensionRegex(filename: string): string {
|
||||||
const match = filename.match(/\.([^.]+)$/);
|
const match = filename.match(/\.([^.]+)$/);
|
||||||
@ -30,21 +31,22 @@ const PdfDrawer = ({
|
|||||||
// const [loaded, setLoaded] = useState(false);
|
// const [loaded, setLoaded] = useState(false);
|
||||||
const url = getDocumentUrl();
|
const url = getDocumentUrl();
|
||||||
|
|
||||||
console.log('chunk--->', chunk.docnm_kwd, url);
|
|
||||||
const [fileType, setFileType] = useState('');
|
const [fileType, setFileType] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chunk.docnm_kwd) {
|
if (chunk.docnm_kwd || chunk.document_name) {
|
||||||
const type = getFileExtensionRegex(chunk.docnm_kwd);
|
const type = getFileExtensionRegex(
|
||||||
|
chunk.docnm_kwd || chunk.document_name,
|
||||||
|
);
|
||||||
setFileType(type);
|
setFileType(type);
|
||||||
}
|
}
|
||||||
}, [chunk.docnm_kwd]);
|
}, [chunk.docnm_kwd, chunk.document_name]);
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
<FileIcon name={chunk.docnm_kwd || chunk.document_name}></FileIcon>
|
||||||
{chunk.docnm_kwd}
|
{chunk.docnm_kwd || chunk.document_name}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
|
|||||||
140
web/src/pages/next-search/embed-app-modal.tsx
Normal file
140
web/src/pages/next-search/embed-app-modal.tsx
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import HightLightMarkdown from '@/components/highlight-markdown';
|
||||||
|
import { Modal } from '@/components/ui/modal/modal';
|
||||||
|
import { RAGFlowSelect } from '@/components/ui/select';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import {
|
||||||
|
LanguageAbbreviation,
|
||||||
|
LanguageAbbreviationMap,
|
||||||
|
} from '@/constants/common';
|
||||||
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
type IEmbedAppModalProps = {
|
||||||
|
open: any;
|
||||||
|
url: string;
|
||||||
|
token: string;
|
||||||
|
from: string;
|
||||||
|
beta: string;
|
||||||
|
setOpen: (e: any) => void;
|
||||||
|
tenantId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EmbedAppModal = (props: IEmbedAppModalProps) => {
|
||||||
|
const { t } = useTranslate('chat');
|
||||||
|
const { open, setOpen, token = '', from, beta = '', url, tenantId } = props;
|
||||||
|
const [hideAvatar, setHideAvatar] = useState(false);
|
||||||
|
const [locale, setLocale] = useState('');
|
||||||
|
|
||||||
|
const languageOptions = useMemo(() => {
|
||||||
|
return Object.values(LanguageAbbreviation).map((x) => ({
|
||||||
|
label: LanguageAbbreviationMap[x],
|
||||||
|
value: x,
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const generateIframeSrc = useCallback(() => {
|
||||||
|
// const { visibleAvatar, locale } = values;
|
||||||
|
let src = `${location.origin}${url}?shared_id=${token}&from=${from}&auth=${beta}&tenantId=${tenantId}`;
|
||||||
|
if (hideAvatar) {
|
||||||
|
src += '&visible_avatar=1';
|
||||||
|
}
|
||||||
|
if (locale) {
|
||||||
|
src += `&locale=${locale}`;
|
||||||
|
}
|
||||||
|
return src;
|
||||||
|
}, [beta, from, token, hideAvatar, locale, url, tenantId]);
|
||||||
|
|
||||||
|
// ... existing code ...
|
||||||
|
const text = useMemo(() => {
|
||||||
|
const iframeSrc = generateIframeSrc();
|
||||||
|
return `\`\`\`html
|
||||||
|
<iframe
|
||||||
|
src="${iframeSrc}"
|
||||||
|
style="width: 100%; height: 100%; min-height: 600px"
|
||||||
|
frameborder="0">
|
||||||
|
</iframe>
|
||||||
|
\`\`\``;
|
||||||
|
}, [generateIframeSrc]);
|
||||||
|
// ... existing code ...
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t('embedIntoSite', { keyPrefix: 'common' })}
|
||||||
|
className="!bg-bg-base !text-text-disabled"
|
||||||
|
open={open}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
showfooter={false}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<div className="w-full">
|
||||||
|
{/* Hide Avatar Toggle */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block text-sm font-medium mb-2">
|
||||||
|
{t('avatarHidden')}
|
||||||
|
</label>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Switch
|
||||||
|
checked={hideAvatar}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
setHideAvatar(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Locale Select */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block text-sm font-medium mb-2">Locale</label>
|
||||||
|
<RAGFlowSelect
|
||||||
|
placeholder="Select a locale"
|
||||||
|
value={locale}
|
||||||
|
onChange={(value) => setLocale(value)}
|
||||||
|
options={languageOptions}
|
||||||
|
></RAGFlowSelect>
|
||||||
|
</div>
|
||||||
|
{/* Embed Code */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block text-sm font-medium mb-2">Embed code</label>
|
||||||
|
{/* <div className=" border rounded-lg"> */}
|
||||||
|
{/* <pre className="text-sm whitespace-pre-wrap">{text}</pre> */}
|
||||||
|
<HightLightMarkdown>{text}</HightLightMarkdown>
|
||||||
|
{/* </div> */}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ID Field */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium mb-2">ID</label>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={token}
|
||||||
|
readOnly
|
||||||
|
className="flex-1 px-4 py-2 border border-gray-700 rounded-lg bg-bg-base focus:outline-none"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigator.clipboard.writeText(token)}
|
||||||
|
className="ml-2 p-2 text-gray-400 hover:text-white transition-colors"
|
||||||
|
title="Copy ID"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h10a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default EmbedAppModal;
|
||||||
488
web/src/pages/next-search/hooks.ts
Normal file
488
web/src/pages/next-search/hooks.ts
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
import message from '@/components/ui/message';
|
||||||
|
import { SharedFrom } from '@/constants/chat';
|
||||||
|
import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
|
||||||
|
import {
|
||||||
|
useGetPaginationWithRouter,
|
||||||
|
useSendMessageWithSse,
|
||||||
|
} from '@/hooks/logic-hooks';
|
||||||
|
import { useSetPaginationParams } from '@/hooks/route-hook';
|
||||||
|
import { useKnowledgeBaseId } from '@/hooks/use-knowledge-request';
|
||||||
|
import { ResponsePostType } from '@/interfaces/database/base';
|
||||||
|
import { IAnswer } from '@/interfaces/database/chat';
|
||||||
|
import { ITestingResult } from '@/interfaces/database/knowledge';
|
||||||
|
import { IAskRequestBody } from '@/interfaces/request/chat';
|
||||||
|
import chatService from '@/services/chat-service';
|
||||||
|
import kbService from '@/services/knowledge-service';
|
||||||
|
import searchService from '@/services/search-service';
|
||||||
|
import api from '@/utils/api';
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { has, isEmpty, trim } from 'lodash';
|
||||||
|
import {
|
||||||
|
ChangeEventHandler,
|
||||||
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { useSearchParams } from 'umi';
|
||||||
|
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||||
|
import { useShowMindMapDrawer } from '../search/hooks';
|
||||||
|
import { useClickDrawer } from './document-preview-modal/hooks';
|
||||||
|
|
||||||
|
export interface ISearchingProps {
|
||||||
|
searchText?: string;
|
||||||
|
data: ISearchAppDetailProps;
|
||||||
|
setIsSearching?: Dispatch<SetStateAction<boolean>>;
|
||||||
|
setSearchText?: Dispatch<SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ISearchReturnProps = ReturnType<typeof useSearching>;
|
||||||
|
|
||||||
|
export const useGetSharedSearchParams = () => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const data_prefix = 'data_';
|
||||||
|
const data = Object.fromEntries(
|
||||||
|
searchParams
|
||||||
|
.entries()
|
||||||
|
.filter(([key]) => key.startsWith(data_prefix))
|
||||||
|
.map(([key, value]) => [key.replace(data_prefix, ''), value]),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
from: searchParams.get('from') as SharedFrom,
|
||||||
|
sharedId: searchParams.get('shared_id'),
|
||||||
|
locale: searchParams.get('locale'),
|
||||||
|
tenantId: searchParams.get('tenantId'),
|
||||||
|
data: data,
|
||||||
|
visibleAvatar: searchParams.get('visible_avatar')
|
||||||
|
? searchParams.get('visible_avatar') !== '1'
|
||||||
|
: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSearchFetchMindMap = () => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const sharedId = searchParams.get('shared_id');
|
||||||
|
const fetchMindMapFunc = sharedId
|
||||||
|
? searchService.mindmapShare
|
||||||
|
: chatService.getMindMap;
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isPending: loading,
|
||||||
|
mutateAsync,
|
||||||
|
} = useMutation({
|
||||||
|
mutationKey: ['fetchMindMap'],
|
||||||
|
gcTime: 0,
|
||||||
|
mutationFn: async (params: IAskRequestBody) => {
|
||||||
|
try {
|
||||||
|
const ret = await fetchMindMapFunc(params);
|
||||||
|
return ret?.data?.data ?? {};
|
||||||
|
} catch (error: any) {
|
||||||
|
if (has(error, 'message')) {
|
||||||
|
message.error(error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data, loading, fetchMindMap: mutateAsync };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTestChunkRetrieval = (
|
||||||
|
tenantId?: string,
|
||||||
|
): ResponsePostType<ITestingResult> & {
|
||||||
|
testChunk: (...params: any[]) => void;
|
||||||
|
} => {
|
||||||
|
const knowledgeBaseId = useKnowledgeBaseId();
|
||||||
|
const { page, size: pageSize } = useSetPaginationParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const shared_id = searchParams.get('shared_id');
|
||||||
|
const retrievalTestFunc = shared_id
|
||||||
|
? kbService.retrievalTestShare
|
||||||
|
: kbService.retrieval_test;
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isPending: loading,
|
||||||
|
mutateAsync,
|
||||||
|
} = useMutation({
|
||||||
|
mutationKey: ['testChunk'], // This method is invalid
|
||||||
|
gcTime: 0,
|
||||||
|
mutationFn: async (values: any) => {
|
||||||
|
const { data } = await retrievalTestFunc({
|
||||||
|
...values,
|
||||||
|
kb_id: values.kb_id ?? knowledgeBaseId,
|
||||||
|
page,
|
||||||
|
size: pageSize,
|
||||||
|
tenant_id: tenantId,
|
||||||
|
});
|
||||||
|
if (data.code === 0) {
|
||||||
|
const res = data.data;
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
documents: res.doc_aggs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
data?.data ?? {
|
||||||
|
chunks: [],
|
||||||
|
documents: [],
|
||||||
|
total: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data ?? { chunks: [], documents: [], total: 0 },
|
||||||
|
loading,
|
||||||
|
testChunk: mutateAsync,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTestChunkAllRetrieval = (
|
||||||
|
tenantId?: string,
|
||||||
|
): ResponsePostType<ITestingResult> & {
|
||||||
|
testChunkAll: (...params: any[]) => void;
|
||||||
|
} => {
|
||||||
|
const knowledgeBaseId = useKnowledgeBaseId();
|
||||||
|
const { page, size: pageSize } = useSetPaginationParams();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const shared_id = searchParams.get('shared_id');
|
||||||
|
const retrievalTestFunc = shared_id
|
||||||
|
? kbService.retrievalTestShare
|
||||||
|
: kbService.retrieval_test;
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isPending: loading,
|
||||||
|
mutateAsync,
|
||||||
|
} = useMutation({
|
||||||
|
mutationKey: ['testChunkAll'], // This method is invalid
|
||||||
|
gcTime: 0,
|
||||||
|
mutationFn: async (values: any) => {
|
||||||
|
const { data } = await retrievalTestFunc({
|
||||||
|
...values,
|
||||||
|
kb_id: values.kb_id ?? knowledgeBaseId,
|
||||||
|
doc_ids: [],
|
||||||
|
page,
|
||||||
|
size: pageSize,
|
||||||
|
tenant_id: tenantId,
|
||||||
|
});
|
||||||
|
if (data.code === 0) {
|
||||||
|
const res = data.data;
|
||||||
|
return {
|
||||||
|
...res,
|
||||||
|
documents: res.doc_aggs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
data?.data ?? {
|
||||||
|
chunks: [],
|
||||||
|
documents: [],
|
||||||
|
total: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data ?? { chunks: [], documents: [], total: 0 },
|
||||||
|
loading,
|
||||||
|
testChunkAll: mutateAsync,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTestRetrieval = (
|
||||||
|
kbIds: string[],
|
||||||
|
searchStr: string,
|
||||||
|
sendingLoading: boolean,
|
||||||
|
) => {
|
||||||
|
const { testChunk, loading } = useTestChunkRetrieval();
|
||||||
|
const { pagination } = useGetPaginationWithRouter();
|
||||||
|
|
||||||
|
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const handleTestChunk = useCallback(() => {
|
||||||
|
const q = trim(searchStr);
|
||||||
|
if (sendingLoading || isEmpty(q)) return;
|
||||||
|
|
||||||
|
testChunk({
|
||||||
|
kb_id: kbIds,
|
||||||
|
highlight: true,
|
||||||
|
question: q,
|
||||||
|
doc_ids: Array.isArray(selectedDocumentIds) ? selectedDocumentIds : [],
|
||||||
|
page: pagination.current,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
sendingLoading,
|
||||||
|
searchStr,
|
||||||
|
kbIds,
|
||||||
|
testChunk,
|
||||||
|
selectedDocumentIds,
|
||||||
|
pagination,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleTestChunk();
|
||||||
|
}, [handleTestChunk]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loading,
|
||||||
|
selectedDocumentIds,
|
||||||
|
setSelectedDocumentIds,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const useFetchRelatedQuestions = (tenantId?: string) => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const shared_id = searchParams.get('shared_id');
|
||||||
|
const retrievalTestFunc = shared_id
|
||||||
|
? searchService.getRelatedQuestionsShare
|
||||||
|
: chatService.getRelatedQuestions;
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isPending: loading,
|
||||||
|
mutateAsync,
|
||||||
|
} = useMutation({
|
||||||
|
mutationKey: ['fetchRelatedQuestions'],
|
||||||
|
gcTime: 0,
|
||||||
|
mutationFn: async (question: string): Promise<string[]> => {
|
||||||
|
const { data } = await retrievalTestFunc({
|
||||||
|
question,
|
||||||
|
tenant_id: tenantId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data?.data ?? [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data, loading, fetchRelatedQuestions: mutateAsync };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
|
||||||
|
const { sharedId } = useGetSharedSearchParams();
|
||||||
|
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
|
||||||
|
sharedId ? api.askShare : api.ask,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { testChunk, loading } = useTestChunkRetrieval(tenantId);
|
||||||
|
const { testChunkAll } = useTestChunkAllRetrieval(tenantId);
|
||||||
|
const [sendingLoading, setSendingLoading] = useState(false);
|
||||||
|
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
||||||
|
const { fetchRelatedQuestions, data: relatedQuestions } =
|
||||||
|
useFetchRelatedQuestions(tenantId);
|
||||||
|
const [searchStr, setSearchStr] = useState<string>('');
|
||||||
|
const [isFirstRender, setIsFirstRender] = useState(true);
|
||||||
|
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||||
|
|
||||||
|
const sendQuestion = useCallback(
|
||||||
|
(question: string) => {
|
||||||
|
const q = trim(question);
|
||||||
|
if (isEmpty(q)) return;
|
||||||
|
setPagination({ page: 1 });
|
||||||
|
setIsFirstRender(false);
|
||||||
|
setCurrentAnswer({} as IAnswer);
|
||||||
|
setSendingLoading(true);
|
||||||
|
send({ kb_ids: kbIds, question: q, tenantId });
|
||||||
|
testChunk({
|
||||||
|
kb_id: kbIds,
|
||||||
|
highlight: true,
|
||||||
|
question: q,
|
||||||
|
page: 1,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchRelatedQuestions(q);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
send,
|
||||||
|
testChunk,
|
||||||
|
kbIds,
|
||||||
|
fetchRelatedQuestions,
|
||||||
|
setPagination,
|
||||||
|
pagination.pageSize,
|
||||||
|
tenantId,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSearchStrChange: ChangeEventHandler<HTMLInputElement> =
|
||||||
|
useCallback((e) => {
|
||||||
|
setSearchStr(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClickRelatedQuestion = useCallback(
|
||||||
|
(question: string) => () => {
|
||||||
|
if (sendingLoading) return;
|
||||||
|
|
||||||
|
setSearchStr(question);
|
||||||
|
sendQuestion(question);
|
||||||
|
},
|
||||||
|
[sendQuestion, sendingLoading],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleTestChunk = useCallback(
|
||||||
|
(documentIds: string[], page: number = 1, size: number = 10) => {
|
||||||
|
const q = trim(searchStr);
|
||||||
|
if (sendingLoading || isEmpty(q)) return;
|
||||||
|
|
||||||
|
testChunk({
|
||||||
|
kb_id: kbIds,
|
||||||
|
highlight: true,
|
||||||
|
question: q,
|
||||||
|
doc_ids: documentIds ?? selectedDocumentIds,
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
|
||||||
|
testChunkAll({
|
||||||
|
kb_id: kbIds,
|
||||||
|
highlight: true,
|
||||||
|
question: q,
|
||||||
|
doc_ids: [],
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[
|
||||||
|
searchStr,
|
||||||
|
sendingLoading,
|
||||||
|
testChunk,
|
||||||
|
kbIds,
|
||||||
|
selectedDocumentIds,
|
||||||
|
testChunkAll,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isEmpty(answer)) {
|
||||||
|
setCurrentAnswer(answer);
|
||||||
|
}
|
||||||
|
}, [answer]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (done) {
|
||||||
|
setSendingLoading(false);
|
||||||
|
}
|
||||||
|
}, [done]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendQuestion,
|
||||||
|
handleSearchStrChange,
|
||||||
|
handleClickRelatedQuestion,
|
||||||
|
handleTestChunk,
|
||||||
|
setSelectedDocumentIds,
|
||||||
|
loading,
|
||||||
|
sendingLoading,
|
||||||
|
answer: currentAnswer,
|
||||||
|
relatedQuestions: relatedQuestions?.slice(0, 5) ?? [],
|
||||||
|
searchStr,
|
||||||
|
setSearchStr,
|
||||||
|
isFirstRender,
|
||||||
|
selectedDocumentIds,
|
||||||
|
isSearchStrEmpty: isEmpty(trim(searchStr)),
|
||||||
|
stopOutputMessage,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSearching = ({
|
||||||
|
searchText,
|
||||||
|
data: searchData,
|
||||||
|
setSearchText,
|
||||||
|
}: ISearchingProps) => {
|
||||||
|
const { tenantId } = useGetSharedSearchParams();
|
||||||
|
const {
|
||||||
|
sendQuestion,
|
||||||
|
handleClickRelatedQuestion,
|
||||||
|
handleTestChunk,
|
||||||
|
setSelectedDocumentIds,
|
||||||
|
answer,
|
||||||
|
sendingLoading,
|
||||||
|
relatedQuestions,
|
||||||
|
searchStr,
|
||||||
|
loading,
|
||||||
|
isFirstRender,
|
||||||
|
selectedDocumentIds,
|
||||||
|
isSearchStrEmpty,
|
||||||
|
setSearchStr,
|
||||||
|
stopOutputMessage,
|
||||||
|
} = useSendQuestion(searchData.search_config.kb_ids, tenantId as string);
|
||||||
|
|
||||||
|
const handleSearchStrChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
console.log('handleSearchStrChange', value);
|
||||||
|
setSearchStr(value);
|
||||||
|
},
|
||||||
|
[setSearchStr],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
||||||
|
useClickDrawer();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchText) {
|
||||||
|
setSearchStr(searchText);
|
||||||
|
sendQuestion(searchText);
|
||||||
|
setSearchText?.('');
|
||||||
|
}
|
||||||
|
}, [searchText, sendQuestion, setSearchStr, setSearchText]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
mindMapVisible,
|
||||||
|
hideMindMapModal,
|
||||||
|
showMindMapModal,
|
||||||
|
mindMapLoading,
|
||||||
|
mindMap,
|
||||||
|
} = useShowMindMapDrawer(searchData.search_config.kb_ids, searchStr);
|
||||||
|
const { chunks, total } = useSelectTestingResult();
|
||||||
|
|
||||||
|
const handleSearch = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
sendQuestion(value);
|
||||||
|
setSearchStr?.(value);
|
||||||
|
},
|
||||||
|
[setSearchStr, sendQuestion],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||||
|
const onChange = (pageNumber: number, pageSize: number) => {
|
||||||
|
setPagination({ page: pageNumber, pageSize });
|
||||||
|
handleTestChunk(selectedDocumentIds, pageNumber, pageSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendQuestion,
|
||||||
|
handleClickRelatedQuestion,
|
||||||
|
handleSearchStrChange,
|
||||||
|
handleTestChunk,
|
||||||
|
setSelectedDocumentIds,
|
||||||
|
answer,
|
||||||
|
sendingLoading,
|
||||||
|
relatedQuestions,
|
||||||
|
searchStr,
|
||||||
|
loading,
|
||||||
|
isFirstRender,
|
||||||
|
selectedDocumentIds,
|
||||||
|
isSearchStrEmpty,
|
||||||
|
setSearchStr,
|
||||||
|
stopOutputMessage,
|
||||||
|
|
||||||
|
visible,
|
||||||
|
hideModal,
|
||||||
|
documentId,
|
||||||
|
selectedChunk,
|
||||||
|
clickDocumentButton,
|
||||||
|
mindMapVisible,
|
||||||
|
hideMindMapModal,
|
||||||
|
showMindMapModal,
|
||||||
|
mindMapLoading,
|
||||||
|
mindMap,
|
||||||
|
chunks,
|
||||||
|
total,
|
||||||
|
handleSearch,
|
||||||
|
pagination,
|
||||||
|
onChange,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -8,13 +8,17 @@ import {
|
|||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from '@/components/ui/breadcrumb';
|
} from '@/components/ui/breadcrumb';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { SharedFrom } from '@/constants/chat';
|
||||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { Settings } from 'lucide-react';
|
import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
|
||||||
|
import { Send, Settings } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useFetchTokenListBeforeOtherStep } from '../agent/hooks/use-show-dialog';
|
||||||
import {
|
import {
|
||||||
ISearchAppDetailProps,
|
ISearchAppDetailProps,
|
||||||
useFetchSearchDetail,
|
useFetchSearchDetail,
|
||||||
} from '../next-searches/hooks';
|
} from '../next-searches/hooks';
|
||||||
|
import EmbedAppModal from './embed-app-modal';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import SearchHome from './search-home';
|
import SearchHome from './search-home';
|
||||||
import { SearchSetting } from './search-setting';
|
import { SearchSetting } from './search-setting';
|
||||||
@ -24,9 +28,15 @@ export default function SearchPage() {
|
|||||||
const { navigateToSearchList } = useNavigatePage();
|
const { navigateToSearchList } = useNavigatePage();
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
const { data: SearchData } = useFetchSearchDetail();
|
const { data: SearchData } = useFetchSearchDetail();
|
||||||
|
const { beta, handleOperate } = useFetchTokenListBeforeOtherStep();
|
||||||
const [openSetting, setOpenSetting] = useState(false);
|
const [openSetting, setOpenSetting] = useState(false);
|
||||||
|
const [openEmbed, setOpenEmbed] = useState(false);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState('');
|
||||||
|
const { data: tenantInfo } = useFetchTenantInfo();
|
||||||
|
const tenantId = tenantInfo.tenant_id;
|
||||||
|
useEffect(() => {
|
||||||
|
handleOperate();
|
||||||
|
}, [handleOperate]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSearching) {
|
if (isSearching) {
|
||||||
setOpenSetting(false);
|
setOpenSetting(false);
|
||||||
@ -81,8 +91,37 @@ export default function SearchPage() {
|
|||||||
data={SearchData as ISearchAppDetailProps}
|
data={SearchData as ISearchAppDetailProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
<EmbedAppModal
|
||||||
|
open={openEmbed}
|
||||||
|
setOpen={setOpenEmbed}
|
||||||
|
url="/next-search/share"
|
||||||
|
token={SearchData?.id as string}
|
||||||
|
from={SharedFrom.Search}
|
||||||
|
beta={beta}
|
||||||
|
tenantId={tenantId}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// <EmbedDialog
|
||||||
|
// visible={openEmbed}
|
||||||
|
// hideModal={setOpenEmbed}
|
||||||
|
// token={SearchData?.id as string}
|
||||||
|
// from={SharedFrom.Search}
|
||||||
|
// beta={beta}
|
||||||
|
// isAgent={false}
|
||||||
|
// ></EmbedDialog>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="absolute right-5 top-12 ">
|
||||||
|
<Button
|
||||||
|
className="bg-text-primary text-bg-base border-b-[#00BEB4] border-b-2"
|
||||||
|
onClick={() => setOpenEmbed(!openEmbed)}
|
||||||
|
>
|
||||||
|
<Send />
|
||||||
|
<div>Embed App</div>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isSearching && (
|
{!isSearching && (
|
||||||
<div className="absolute left-5 bottom-12 ">
|
<div className="absolute left-5 bottom-12 ">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const percent = usePendingMindMap();
|
const percent = usePendingMindMap();
|
||||||
return (
|
return (
|
||||||
<div className="w-[400px] h-[420px]">
|
<div className="w-full h-full">
|
||||||
<div className="flex w-full justify-between items-center mb-2">
|
<div className="flex w-full justify-between items-center mb-2">
|
||||||
<div className="text-text-primary font-medium text-base">
|
<div className="text-text-primary font-medium text-base">
|
||||||
{t('chunk.mind')}
|
{t('chunk.mind')}
|
||||||
@ -32,11 +32,14 @@ const MindMapDrawer = ({ data, hideModal, visible, loading }: IProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<div className="bg-bg-card rounded-lg p-4 w-[400px] h-[380px]">
|
<div className="bg-bg-card rounded-lg p-4 w-full h-full">
|
||||||
<IndentedTree
|
<IndentedTree
|
||||||
data={data}
|
data={data}
|
||||||
show
|
show
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
></IndentedTree>
|
></IndentedTree>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Input } from '@/components/originui/input';
|
import { Input } from '@/components/originui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
@ -68,42 +67,6 @@ export default function SearchPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
|
|
||||||
<p className="text-text-primary mb-2 text-xl">Related Search</p>
|
|
||||||
<div className="mt-2 flex flex-wrap justify-start gap-2">
|
|
||||||
<Button
|
|
||||||
variant="transparent"
|
|
||||||
className="bg-bg-card text-text-secondary"
|
|
||||||
>
|
|
||||||
Related Search
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="transparent"
|
|
||||||
className="bg-bg-card text-text-secondary"
|
|
||||||
>
|
|
||||||
Related Search Related SearchRelated Search
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="transparent"
|
|
||||||
className="bg-bg-card text-text-secondary"
|
|
||||||
>
|
|
||||||
Related Search Search
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="transparent"
|
|
||||||
className="bg-bg-card text-text-secondary"
|
|
||||||
>
|
|
||||||
Related Search Related SearchRelated Search
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="transparent"
|
|
||||||
className="bg-bg-card text-text-secondary"
|
|
||||||
>
|
|
||||||
Related Search
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import {
|
import {
|
||||||
MultiSelect,
|
MultiSelect,
|
||||||
MultiSelectOptionType,
|
MultiSelectOptionType,
|
||||||
@ -42,6 +41,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ISearchAppDetailProps,
|
ISearchAppDetailProps,
|
||||||
IUpdateSearchProps,
|
IUpdateSearchProps,
|
||||||
|
IllmSettingProps,
|
||||||
useUpdateSearch,
|
useUpdateSearch,
|
||||||
} from '../next-searches/hooks';
|
} from '../next-searches/hooks';
|
||||||
import {
|
import {
|
||||||
@ -55,14 +55,6 @@ interface SearchSettingProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
data: ISearchAppDetailProps;
|
data: ISearchAppDetailProps;
|
||||||
}
|
}
|
||||||
interface ISubmitLlmSettingProps {
|
|
||||||
llm_id: string;
|
|
||||||
parameter: string;
|
|
||||||
temperature?: number;
|
|
||||||
top_p?: number;
|
|
||||||
frequency_penalty?: number;
|
|
||||||
presence_penalty?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SearchSettingFormSchema = z
|
const SearchSettingFormSchema = z
|
||||||
.object({
|
.object({
|
||||||
@ -120,16 +112,19 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
|
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
|
||||||
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
||||||
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
||||||
|
const descriptionDefaultValue = 'You are an intelligent assistant.';
|
||||||
const resetForm = useCallback(() => {
|
const resetForm = useCallback(() => {
|
||||||
formMethods.reset({
|
formMethods.reset({
|
||||||
search_id: data?.id,
|
search_id: data?.id,
|
||||||
name: data?.name || '',
|
name: data?.name || '',
|
||||||
avatar: data?.avatar || '',
|
avatar: data?.avatar || '',
|
||||||
description: data?.description || 'You are an intelligent assistant.',
|
description: data?.description || descriptionDefaultValue,
|
||||||
search_config: {
|
search_config: {
|
||||||
kb_ids: search_config?.kb_ids || [],
|
kb_ids: search_config?.kb_ids || [],
|
||||||
vector_similarity_weight: search_config?.vector_similarity_weight || 20,
|
vector_similarity_weight:
|
||||||
|
(search_config?.vector_similarity_weight
|
||||||
|
? 1 - search_config?.vector_similarity_weight
|
||||||
|
: 0.3) || 0.3,
|
||||||
web_search: search_config?.web_search || false,
|
web_search: search_config?.web_search || false,
|
||||||
doc_ids: [],
|
doc_ids: [],
|
||||||
similarity_threshold: 0.0,
|
similarity_threshold: 0.0,
|
||||||
@ -198,8 +193,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}, [avatarFile]);
|
}, [avatarFile]);
|
||||||
const { list: datasetListOrigin, loading: datasetLoading } =
|
const { list: datasetListOrigin } = useFetchKnowledgeList();
|
||||||
useFetchKnowledgeList();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const datasetListMap = datasetListOrigin.map((item: IKnowledge) => {
|
const datasetListMap = datasetListOrigin.map((item: IKnowledge) => {
|
||||||
@ -259,7 +253,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const { search_config, ...other_formdata } = formData;
|
const { search_config, ...other_formdata } = formData;
|
||||||
const { llm_setting, ...other_config } = search_config;
|
const { llm_setting, vector_similarity_weight, ...other_config } =
|
||||||
|
search_config;
|
||||||
const llmSetting = {
|
const llmSetting = {
|
||||||
llm_id: llm_setting.llm_id,
|
llm_id: llm_setting.llm_id,
|
||||||
parameter: llm_setting.parameter,
|
parameter: llm_setting.parameter,
|
||||||
@ -267,7 +262,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
top_p: llm_setting.top_p,
|
top_p: llm_setting.top_p,
|
||||||
frequency_penalty: llm_setting.frequency_penalty,
|
frequency_penalty: llm_setting.frequency_penalty,
|
||||||
presence_penalty: llm_setting.presence_penalty,
|
presence_penalty: llm_setting.presence_penalty,
|
||||||
} as ISubmitLlmSettingProps;
|
} as IllmSettingProps;
|
||||||
|
|
||||||
if (!llm_setting.frequencyPenaltyEnabled) {
|
if (!llm_setting.frequencyPenaltyEnabled) {
|
||||||
delete llmSetting.frequency_penalty;
|
delete llmSetting.frequency_penalty;
|
||||||
}
|
}
|
||||||
@ -284,6 +280,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
...other_formdata,
|
...other_formdata,
|
||||||
search_config: {
|
search_config: {
|
||||||
...other_config,
|
...other_config,
|
||||||
|
vector_similarity_weight: 1 - vector_similarity_weight,
|
||||||
llm_setting: { ...llmSetting },
|
llm_setting: { ...llmSetting },
|
||||||
},
|
},
|
||||||
tenant_id: systemSetting.tenant_id,
|
tenant_id: systemSetting.tenant_id,
|
||||||
@ -355,46 +352,54 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Avatar</FormLabel>
|
<FormLabel>Avatar</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="relative group">
|
<div className="relative group flex items-end gap-2">
|
||||||
{!avatarBase64Str ? (
|
<div>
|
||||||
<div className="w-[64px] h-[64px] grid place-content-center border border-dashed rounded-md">
|
{!avatarBase64Str ? (
|
||||||
<div className="flex flex-col items-center">
|
<div className="w-[64px] h-[64px] grid place-content-center border border-dashed rounded-md">
|
||||||
<Upload />
|
<div className="flex flex-col items-center">
|
||||||
<p>{t('common.upload')}</p>
|
<Upload />
|
||||||
|
<p>{t('common.upload')}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
) : (
|
<div className="w-[64px] h-[64px] relative grid place-content-center">
|
||||||
<div className="w-[64px] h-[64px] relative grid place-content-center">
|
<RAGFlowAvatar
|
||||||
<RAGFlowAvatar
|
avatar={avatarBase64Str}
|
||||||
avatar={avatarBase64Str}
|
name={data.name}
|
||||||
name={data.name}
|
className="w-[64px] h-[64px] rounded-md block"
|
||||||
className="w-[64px] h-[64px] rounded-md block"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-[#000]/20 group-hover:bg-[#000]/60">
|
|
||||||
<Pencil
|
|
||||||
size={20}
|
|
||||||
className="absolute right-2 bottom-0 opacity-50 hidden group-hover:block"
|
|
||||||
/>
|
/>
|
||||||
|
<div className="absolute inset-0 bg-[#000]/20 group-hover:bg-[#000]/60">
|
||||||
|
<Pencil
|
||||||
|
size={20}
|
||||||
|
className="absolute right-2 bottom-0 opacity-50 hidden group-hover:block"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
<input
|
||||||
<input
|
placeholder=""
|
||||||
placeholder=""
|
// {...field}
|
||||||
// {...field}
|
type="file"
|
||||||
type="file"
|
title=""
|
||||||
title=""
|
accept="image/*"
|
||||||
accept="image/*"
|
className="absolute w-[64px] top-0 left-0 h-full opacity-0 cursor-pointer"
|
||||||
className="absolute w-[64px] top-0 left-0 h-full opacity-0 cursor-pointer"
|
onChange={(ev) => {
|
||||||
onChange={(ev) => {
|
const file = ev.target?.files?.[0];
|
||||||
const file = ev.target?.files?.[0];
|
if (
|
||||||
if (
|
/\.(jpg|jpeg|png|webp|bmp)$/i.test(
|
||||||
/\.(jpg|jpeg|png|webp|bmp)$/i.test(file?.name ?? '')
|
file?.name ?? '',
|
||||||
) {
|
)
|
||||||
setAvatarFile(file!);
|
) {
|
||||||
}
|
setAvatarFile(file!);
|
||||||
ev.target.value = '';
|
}
|
||||||
}}
|
ev.target.value = '';
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="margin-1 text-muted-foreground">
|
||||||
|
{t('knowledgeConfiguration.photoTip')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
@ -410,7 +415,20 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Description</FormLabel>
|
<FormLabel>Description</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="Description" {...field} />
|
<Input
|
||||||
|
placeholder="You are an intelligent assistant."
|
||||||
|
{...field}
|
||||||
|
onFocus={() => {
|
||||||
|
if (field.value === descriptionDefaultValue) {
|
||||||
|
field.onChange('');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
if (field.value === '') {
|
||||||
|
field.onChange(descriptionDefaultValue);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@ -451,26 +469,58 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
name="search_config.vector_similarity_weight"
|
name="search_config.vector_similarity_weight"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-col">
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
<span className="text-destructive mr-1"> *</span>Keyword
|
<span className="text-destructive mr-1"> *</span>Keyword
|
||||||
Similarity Weight
|
Similarity Weight
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<div
|
||||||
<div className="flex justify-between items-center">
|
className={cn(
|
||||||
|
'flex items-center gap-4 justify-between',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
<SingleFormSlider
|
<SingleFormSlider
|
||||||
max={100}
|
{...field}
|
||||||
step={1}
|
max={1}
|
||||||
value={field.value as number}
|
min={0}
|
||||||
onChange={(values) => field.onChange(values)}
|
step={0.01}
|
||||||
></SingleFormSlider>
|
></SingleFormSlider>
|
||||||
<Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
|
</FormControl>
|
||||||
{field.value}
|
<FormControl>
|
||||||
</Label>
|
<Input
|
||||||
</div>
|
type={'number'}
|
||||||
</FormControl>
|
className="h-7 w-20 bg-bg-card"
|
||||||
|
max={1}
|
||||||
|
min={0}
|
||||||
|
step={0.01}
|
||||||
|
{...field}
|
||||||
|
></Input>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
// <FormItem className="flex flex-col">
|
||||||
|
// <FormLabel>
|
||||||
|
// <span className="text-destructive mr-1"> *</span>Keyword
|
||||||
|
// Similarity Weight
|
||||||
|
// </FormLabel>
|
||||||
|
// <FormControl>
|
||||||
|
// {/* <div className="flex justify-between items-center">
|
||||||
|
// <SingleFormSlider
|
||||||
|
// max={100}
|
||||||
|
// step={1}
|
||||||
|
// value={field.value as number}
|
||||||
|
// onChange={(values) => field.onChange(values)}
|
||||||
|
// ></SingleFormSlider>
|
||||||
|
// <Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
|
||||||
|
// {field.value}
|
||||||
|
// </Label>
|
||||||
|
// </div> */}
|
||||||
|
// </FormControl>
|
||||||
|
// <FormMessage />
|
||||||
|
// </FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -528,16 +578,15 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<SingleFormSlider
|
<SingleFormSlider
|
||||||
{...field}
|
{...field}
|
||||||
max={100}
|
max={2048}
|
||||||
min={0}
|
min={0}
|
||||||
step={1}
|
step={1}
|
||||||
></SingleFormSlider>
|
></SingleFormSlider>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
type={'number'}
|
|
||||||
className="h-7 w-20 bg-bg-card"
|
className="h-7 w-20 bg-bg-card"
|
||||||
max={100}
|
max={2048}
|
||||||
min={0}
|
min={0}
|
||||||
step={1}
|
step={1}
|
||||||
{...field}
|
{...field}
|
||||||
|
|||||||
321
web/src/pages/next-search/search-view.tsx
Normal file
321
web/src/pages/next-search/search-view.tsx
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
import { FileIcon } from '@/components/icon-font';
|
||||||
|
import { ImageWithPopover } from '@/components/image';
|
||||||
|
import { Input } from '@/components/originui/input';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
|
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { Spin } from '@/components/ui/spin';
|
||||||
|
import { IReference } from '@/interfaces/database/chat';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
import { TFunction } from 'i18next';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { BrainCircuit, Search, Square, X } from 'lucide-react';
|
||||||
|
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||||
|
import PdfDrawer from './document-preview-modal';
|
||||||
|
import HightLightMarkdown from './highlight-markdown';
|
||||||
|
import { ISearchReturnProps } from './hooks';
|
||||||
|
import './index.less';
|
||||||
|
import MarkdownContent from './markdown-content';
|
||||||
|
import MindMapDrawer from './mindmap-drawer';
|
||||||
|
import RetrievalDocuments from './retrieval-documents';
|
||||||
|
export default function SearchingView({
|
||||||
|
setIsSearching,
|
||||||
|
searchData,
|
||||||
|
handleClickRelatedQuestion,
|
||||||
|
handleTestChunk,
|
||||||
|
setSelectedDocumentIds,
|
||||||
|
answer,
|
||||||
|
sendingLoading,
|
||||||
|
relatedQuestions,
|
||||||
|
loading,
|
||||||
|
isFirstRender,
|
||||||
|
selectedDocumentIds,
|
||||||
|
isSearchStrEmpty,
|
||||||
|
searchStr,
|
||||||
|
stopOutputMessage,
|
||||||
|
visible,
|
||||||
|
hideModal,
|
||||||
|
documentId,
|
||||||
|
selectedChunk,
|
||||||
|
clickDocumentButton,
|
||||||
|
mindMapVisible,
|
||||||
|
hideMindMapModal,
|
||||||
|
showMindMapModal,
|
||||||
|
mindMapLoading,
|
||||||
|
mindMap,
|
||||||
|
chunks,
|
||||||
|
total,
|
||||||
|
handleSearch,
|
||||||
|
pagination,
|
||||||
|
onChange,
|
||||||
|
t,
|
||||||
|
}: ISearchReturnProps & {
|
||||||
|
setIsSearching?: Dispatch<SetStateAction<boolean>>;
|
||||||
|
searchData: ISearchAppDetailProps;
|
||||||
|
t: TFunction<'translation', undefined>;
|
||||||
|
}) {
|
||||||
|
const { t: tt, i18n } = useTranslation();
|
||||||
|
useEffect(() => {
|
||||||
|
const changeLanguage = async () => {
|
||||||
|
await i18n.changeLanguage('zh');
|
||||||
|
};
|
||||||
|
changeLanguage();
|
||||||
|
}, [i18n]);
|
||||||
|
const [searchtext, setSearchtext] = useState<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSearchtext(searchStr);
|
||||||
|
}, [searchStr, setSearchtext]);
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className={cn(
|
||||||
|
'relative w-full flex transition-all justify-start items-center',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* search header */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'relative z-10 px-8 pt-8 flex text-transparent justify-start items-start w-full',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
className={cn(
|
||||||
|
'text-4xl font-bold bg-gradient-to-r from-sky-600 from-30% via-sky-500 via-60% to-emerald-500 bg-clip-text cursor-pointer',
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
setIsSearching?.(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
RAGFlow
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
' rounded-lg text-primary text-xl sticky flex flex-col justify-center w-2/3 max-w-[780px] transform scale-100 ml-16 ',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={cn('flex flex-col justify-start items-start w-full')}>
|
||||||
|
<div className="relative w-full text-primary">
|
||||||
|
<Input
|
||||||
|
placeholder={tt('search.searchGreeting')}
|
||||||
|
className={cn(
|
||||||
|
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-background',
|
||||||
|
)}
|
||||||
|
value={searchtext}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearchtext(e.target.value);
|
||||||
|
}}
|
||||||
|
disabled={sendingLoading}
|
||||||
|
onKeyUp={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSearch(searchtext);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform flex items-center gap-1">
|
||||||
|
<X
|
||||||
|
className="text-text-secondary"
|
||||||
|
size={14}
|
||||||
|
onClick={() => {
|
||||||
|
handleClickRelatedQuestion('');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-text-secondary">|</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded-full bg-white p-1 text-gray-800 shadow w-12 h-8 ml-4"
|
||||||
|
onClick={() => {
|
||||||
|
if (sendingLoading) {
|
||||||
|
stopOutputMessage();
|
||||||
|
} else {
|
||||||
|
handleSearch(searchtext);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sendingLoading ? (
|
||||||
|
<Square size={22} className="m-auto" />
|
||||||
|
) : (
|
||||||
|
<Search size={22} className="m-auto" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* search body */}
|
||||||
|
<div
|
||||||
|
className="w-full mt-5 overflow-auto scrollbar-none "
|
||||||
|
style={{ height: 'calc(100vh - 250px)' }}
|
||||||
|
>
|
||||||
|
{searchData.search_config.summary && !isSearchStrEmpty && (
|
||||||
|
<>
|
||||||
|
<div className="flex justify-start items-start text-text-primary text-2xl">
|
||||||
|
AI Summary
|
||||||
|
</div>
|
||||||
|
{isEmpty(answer) && sendingLoading ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Skeleton className="h-4 w-full bg-bg-card" />
|
||||||
|
<Skeleton className="h-4 w-full bg-bg-card" />
|
||||||
|
<Skeleton className="h-4 w-2/3 bg-bg-card" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
answer.answer && (
|
||||||
|
<div className="border rounded-lg p-4 mt-3 max-h-52 overflow-auto scrollbar-none">
|
||||||
|
<MarkdownContent
|
||||||
|
loading={sendingLoading}
|
||||||
|
content={answer.answer}
|
||||||
|
reference={answer.reference ?? ({} as IReference)}
|
||||||
|
clickDocumentButton={clickDocumentButton}
|
||||||
|
></MarkdownContent>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<div className="w-full border-b border-border-default/80 my-6"></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{/* retrieval documents */}
|
||||||
|
{!isSearchStrEmpty && (
|
||||||
|
<>
|
||||||
|
<div className=" mt-3 w-44 ">
|
||||||
|
<RetrievalDocuments
|
||||||
|
selectedDocumentIds={selectedDocumentIds}
|
||||||
|
setSelectedDocumentIds={setSelectedDocumentIds}
|
||||||
|
onTesting={handleTestChunk}
|
||||||
|
></RetrievalDocuments>
|
||||||
|
</div>
|
||||||
|
<div className="w-full border-b border-border-default/80 my-6"></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<div className="mt-3 ">
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
{chunks?.length > 0 && (
|
||||||
|
<>
|
||||||
|
{chunks.map((chunk, index) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
key={chunk.chunk_id}
|
||||||
|
className="w-full flex flex-col"
|
||||||
|
>
|
||||||
|
<div className="w-full">
|
||||||
|
<ImageWithPopover
|
||||||
|
id={chunk.img_id}
|
||||||
|
></ImageWithPopover>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: DOMPurify.sanitize(
|
||||||
|
`${chunk.highlight}...`,
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
className="text-sm text-text-primary mb-1"
|
||||||
|
></div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="text-text-primary">
|
||||||
|
<HightLightMarkdown>
|
||||||
|
{chunk.content_with_weight}
|
||||||
|
</HightLightMarkdown>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit"
|
||||||
|
onClick={() =>
|
||||||
|
clickDocumentButton(chunk.doc_id, chunk as any)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
||||||
|
{chunk.docnm_kwd}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{index < chunks.length - 1 && (
|
||||||
|
<div className="w-full border-b border-border-default/80 mt-6"></div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Spin>
|
||||||
|
{relatedQuestions?.length > 0 && (
|
||||||
|
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
|
||||||
|
<p className="text-text-primary mb-2 text-xl">
|
||||||
|
Related Search
|
||||||
|
</p>
|
||||||
|
<div className="mt-2 flex flex-wrap justify-start gap-2">
|
||||||
|
{relatedQuestions?.map((x, idx) => (
|
||||||
|
<Button
|
||||||
|
key={idx}
|
||||||
|
variant="transparent"
|
||||||
|
className="bg-bg-card text-text-secondary"
|
||||||
|
onClick={handleClickRelatedQuestion(x)}
|
||||||
|
>
|
||||||
|
Related Search{x}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{total > 0 && (
|
||||||
|
<div className="mt-8 px-8 pb-8">
|
||||||
|
<RAGFlowPagination
|
||||||
|
current={pagination.current}
|
||||||
|
pageSize={pagination.pageSize}
|
||||||
|
total={total}
|
||||||
|
onChange={onChange}
|
||||||
|
></RAGFlowPagination>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{mindMapVisible && (
|
||||||
|
<div className="flex-1 h-[88dvh] z-30 ml-8 mt-5">
|
||||||
|
<MindMapDrawer
|
||||||
|
visible={mindMapVisible}
|
||||||
|
hideModal={hideMindMapModal}
|
||||||
|
data={mindMap}
|
||||||
|
loading={mindMapLoading}
|
||||||
|
></MindMapDrawer>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!mindMapVisible &&
|
||||||
|
!isFirstRender &&
|
||||||
|
!isSearchStrEmpty &&
|
||||||
|
!isEmpty(searchData.search_config.kb_ids) &&
|
||||||
|
searchData.search_config.query_mindmap && (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div
|
||||||
|
className="rounded-lg h-16 w-16 p-0 absolute top-28 right-3 z-30 border cursor-pointer flex justify-center items-center bg-bg-card"
|
||||||
|
onClick={showMindMapModal}
|
||||||
|
>
|
||||||
|
{/* <SvgIcon name="paper-clip" width={24} height={30}></SvgIcon> */}
|
||||||
|
<BrainCircuit size={36} />
|
||||||
|
</div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-fit">{t('chunk.mind')}</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
{visible && (
|
||||||
|
<PdfDrawer
|
||||||
|
visible={visible}
|
||||||
|
hideModal={hideModal}
|
||||||
|
documentId={documentId}
|
||||||
|
chunk={selectedChunk}
|
||||||
|
></PdfDrawer>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,332 +1,33 @@
|
|||||||
import { FileIcon } from '@/components/icon-font';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
import { ImageWithPopover } from '@/components/image';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Input } from '@/components/originui/input';
|
|
||||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from '@/components/ui/popover';
|
|
||||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
|
||||||
import { Spin } from '@/components/ui/spin';
|
|
||||||
import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
|
|
||||||
import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
|
|
||||||
import { IReference } from '@/interfaces/database/chat';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import DOMPurify from 'dompurify';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { BrainCircuit, Search, Square, Tag, X } from 'lucide-react';
|
|
||||||
import {
|
|
||||||
Dispatch,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
} from 'react';
|
|
||||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||||
import { useSendQuestion, useShowMindMapDrawer } from '../search/hooks';
|
import { useSearching } from './hooks';
|
||||||
import PdfDrawer from './document-preview-modal';
|
|
||||||
import HightLightMarkdown from './highlight-markdown';
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import styles from './index.less';
|
import SearchingView from './search-view';
|
||||||
import MarkdownContent from './markdown-content';
|
|
||||||
import MindMapDrawer from './mindmap-drawer';
|
|
||||||
import RetrievalDocuments from './retrieval-documents';
|
|
||||||
export default function SearchingPage({
|
export default function SearchingPage({
|
||||||
searchText,
|
searchText,
|
||||||
data: searchData,
|
data: searchData,
|
||||||
setIsSearching,
|
setIsSearching,
|
||||||
|
setSearchText,
|
||||||
}: {
|
}: {
|
||||||
searchText: string;
|
searchText: string;
|
||||||
setIsSearching: Dispatch<SetStateAction<boolean>>;
|
setIsSearching: Dispatch<SetStateAction<boolean>>;
|
||||||
setSearchText: Dispatch<SetStateAction<string>>;
|
setSearchText: Dispatch<SetStateAction<string>>;
|
||||||
data: ISearchAppDetailProps;
|
data: ISearchAppDetailProps;
|
||||||
}) {
|
}) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const searchingParam = useSearching({
|
||||||
const {
|
searchText,
|
||||||
sendQuestion,
|
data: searchData,
|
||||||
handleClickRelatedQuestion,
|
setIsSearching,
|
||||||
handleSearchStrChange,
|
setSearchText,
|
||||||
handleTestChunk,
|
});
|
||||||
setSelectedDocumentIds,
|
const { t } = useTranslation();
|
||||||
answer,
|
|
||||||
sendingLoading,
|
|
||||||
relatedQuestions,
|
|
||||||
searchStr,
|
|
||||||
loading,
|
|
||||||
isFirstRender,
|
|
||||||
selectedDocumentIds,
|
|
||||||
isSearchStrEmpty,
|
|
||||||
setSearchStr,
|
|
||||||
stopOutputMessage,
|
|
||||||
} = useSendQuestion(searchData.search_config.kb_ids);
|
|
||||||
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
|
|
||||||
useClickDrawer();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (searchText) {
|
|
||||||
setSearchStr(searchText);
|
|
||||||
sendQuestion(searchText);
|
|
||||||
}
|
|
||||||
// regain focus
|
|
||||||
if (inputRef.current) {
|
|
||||||
inputRef.current.focus();
|
|
||||||
}
|
|
||||||
}, [searchText, sendQuestion, setSearchStr]);
|
|
||||||
|
|
||||||
const {
|
|
||||||
mindMapVisible,
|
|
||||||
hideMindMapModal,
|
|
||||||
showMindMapModal,
|
|
||||||
mindMapLoading,
|
|
||||||
mindMap,
|
|
||||||
} = useShowMindMapDrawer(searchData.search_config.kb_ids, searchStr);
|
|
||||||
const { chunks, total } = useSelectTestingResult();
|
|
||||||
const handleSearch = useCallback(() => {
|
|
||||||
sendQuestion(searchStr);
|
|
||||||
}, [searchStr, sendQuestion]);
|
|
||||||
|
|
||||||
const { pagination, setPagination } = useGetPaginationWithRouter();
|
|
||||||
const onChange = (pageNumber: number, pageSize: number) => {
|
|
||||||
setPagination({ page: pageNumber, pageSize });
|
|
||||||
handleTestChunk(selectedDocumentIds, pageNumber, pageSize);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<SearchingView
|
||||||
className={cn(
|
{...searchingParam}
|
||||||
'relative w-full flex transition-all justify-start items-center',
|
searchData={searchData}
|
||||||
)}
|
setIsSearching={setIsSearching}
|
||||||
>
|
t={t}
|
||||||
{/* search header */}
|
/>
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'relative z-10 px-8 pt-8 flex text-transparent justify-start items-start w-full',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<h1
|
|
||||||
className={cn(
|
|
||||||
'text-4xl font-bold bg-gradient-to-r from-sky-600 from-30% via-sky-500 via-60% to-emerald-500 bg-clip-text cursor-pointer',
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
setIsSearching(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
RAGFlow
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
' rounded-lg text-primary text-xl sticky flex flex-col justify-center w-2/3 max-w-[780px] transform scale-100 ml-16 ',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className={cn('flex flex-col justify-start items-start w-full')}>
|
|
||||||
<div className="relative w-full text-primary">
|
|
||||||
<Input
|
|
||||||
ref={inputRef}
|
|
||||||
key="search-input"
|
|
||||||
placeholder="How can I help you today?"
|
|
||||||
className={cn(
|
|
||||||
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-background',
|
|
||||||
)}
|
|
||||||
value={searchStr}
|
|
||||||
onChange={handleSearchStrChange}
|
|
||||||
disabled={sendingLoading}
|
|
||||||
onKeyUp={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleSearch();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform flex items-center gap-1">
|
|
||||||
<X
|
|
||||||
className="text-text-secondary"
|
|
||||||
size={14}
|
|
||||||
onClick={() => {
|
|
||||||
handleClickRelatedQuestion('');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span className="text-text-secondary">|</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="rounded-full bg-white p-1 text-gray-800 shadow w-12 h-8 ml-4"
|
|
||||||
onClick={() => {
|
|
||||||
if (sendingLoading) {
|
|
||||||
stopOutputMessage();
|
|
||||||
} else {
|
|
||||||
handleSearch();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{sendingLoading ? (
|
|
||||||
<Square size={22} className="m-auto" />
|
|
||||||
) : (
|
|
||||||
<Search size={22} className="m-auto" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* search body */}
|
|
||||||
<div
|
|
||||||
className="w-full mt-5 overflow-auto scrollbar-none "
|
|
||||||
style={{ height: 'calc(100vh - 250px)' }}
|
|
||||||
>
|
|
||||||
{searchData.search_config.summary && (
|
|
||||||
<>
|
|
||||||
<div className="flex justify-start items-start text-text-primary text-2xl">
|
|
||||||
AI Summary
|
|
||||||
</div>
|
|
||||||
{isEmpty(answer) && sendingLoading ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Skeleton className="h-4 w-full bg-bg-card" />
|
|
||||||
<Skeleton className="h-4 w-full bg-bg-card" />
|
|
||||||
<Skeleton className="h-4 w-2/3 bg-bg-card" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
answer.answer && (
|
|
||||||
<div className="border rounded-lg p-4 mt-3 max-h-52 overflow-auto scrollbar-none">
|
|
||||||
<MarkdownContent
|
|
||||||
loading={sendingLoading}
|
|
||||||
content={answer.answer}
|
|
||||||
reference={answer.reference ?? ({} as IReference)}
|
|
||||||
clickDocumentButton={clickDocumentButton}
|
|
||||||
></MarkdownContent>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="w-full border-b border-border-default/80 my-6"></div>
|
|
||||||
{/* retrieval documents */}
|
|
||||||
<div className=" mt-3 w-44 ">
|
|
||||||
<RetrievalDocuments
|
|
||||||
selectedDocumentIds={selectedDocumentIds}
|
|
||||||
setSelectedDocumentIds={setSelectedDocumentIds}
|
|
||||||
onTesting={handleTestChunk}
|
|
||||||
></RetrievalDocuments>
|
|
||||||
</div>
|
|
||||||
<div className="w-full border-b border-border-default/80 my-6"></div>
|
|
||||||
<div className="mt-3 ">
|
|
||||||
<Spin spinning={loading}>
|
|
||||||
{chunks?.length > 0 && (
|
|
||||||
<>
|
|
||||||
{chunks.map((chunk, index) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
key={chunk.chunk_id}
|
|
||||||
className="w-full flex flex-col"
|
|
||||||
>
|
|
||||||
<div className="w-full">
|
|
||||||
<ImageWithPopover
|
|
||||||
id={chunk.img_id}
|
|
||||||
></ImageWithPopover>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: DOMPurify.sanitize(
|
|
||||||
`${chunk.highlight}...`,
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
className="text-sm text-text-primary mb-1"
|
|
||||||
></div>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="text-text-primary">
|
|
||||||
<HightLightMarkdown>
|
|
||||||
{chunk.content_with_weight}
|
|
||||||
</HightLightMarkdown>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="flex gap-2 items-center text-xs text-text-secondary border p-1 rounded-lg w-fit"
|
|
||||||
onClick={() =>
|
|
||||||
clickDocumentButton(chunk.doc_id, chunk as any)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FileIcon name={chunk.docnm_kwd}></FileIcon>
|
|
||||||
{chunk.docnm_kwd}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{index < chunks.length - 1 && (
|
|
||||||
<div className="w-full border-b border-border-default/80 mt-6"></div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Spin>
|
|
||||||
{relatedQuestions?.length > 0 && (
|
|
||||||
<div title={t('chat.relatedQuestion')}>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{relatedQuestions?.map((x, idx) => (
|
|
||||||
<Tag
|
|
||||||
key={idx}
|
|
||||||
className={styles.tag}
|
|
||||||
onClick={handleClickRelatedQuestion(x)}
|
|
||||||
>
|
|
||||||
{x}
|
|
||||||
</Tag>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-8 px-8 pb-8">
|
|
||||||
<RAGFlowPagination
|
|
||||||
current={pagination.current}
|
|
||||||
pageSize={pagination.pageSize}
|
|
||||||
total={total}
|
|
||||||
onChange={onChange}
|
|
||||||
></RAGFlowPagination>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!mindMapVisible &&
|
|
||||||
!isFirstRender &&
|
|
||||||
!isSearchStrEmpty &&
|
|
||||||
!isEmpty(searchData.search_config.kb_ids) && (
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
className="rounded-lg h-8 w-8 p-0 absolute top-28 right-3 z-30"
|
|
||||||
variant={'transparent'}
|
|
||||||
onClick={showMindMapModal}
|
|
||||||
>
|
|
||||||
{/* <SvgIcon name="paper-clip" width={24} height={30}></SvgIcon> */}
|
|
||||||
<BrainCircuit size={24} />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-fit">{t('chunk.mind')}</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
{visible && (
|
|
||||||
<PdfDrawer
|
|
||||||
visible={visible}
|
|
||||||
hideModal={hideModal}
|
|
||||||
documentId={documentId}
|
|
||||||
chunk={selectedChunk}
|
|
||||||
></PdfDrawer>
|
|
||||||
)}
|
|
||||||
{mindMapVisible && (
|
|
||||||
<div className="absolute top-20 right-16 z-30">
|
|
||||||
<MindMapDrawer
|
|
||||||
visible={mindMapVisible}
|
|
||||||
hideModal={hideMindMapModal}
|
|
||||||
data={mindMap}
|
|
||||||
loading={mindMapLoading}
|
|
||||||
></MindMapDrawer>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</section>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
35
web/src/pages/next-search/share/index.tsx
Normal file
35
web/src/pages/next-search/share/index.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import i18n from '@/locales/config';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import {
|
||||||
|
ISearchAppDetailProps,
|
||||||
|
useFetchSearchDetail,
|
||||||
|
} from '../../next-searches/hooks';
|
||||||
|
import { useGetSharedSearchParams, useSearching } from '../hooks';
|
||||||
|
import '../index.less';
|
||||||
|
import SearchingView from '../search-view';
|
||||||
|
export default function SearchingPage() {
|
||||||
|
const { tenantId, locale } = useGetSharedSearchParams();
|
||||||
|
const {
|
||||||
|
data: searchData = {
|
||||||
|
search_config: { kb_ids: [] },
|
||||||
|
} as unknown as ISearchAppDetailProps,
|
||||||
|
} = useFetchSearchDetail(tenantId as string);
|
||||||
|
const searchingParam = useSearching({
|
||||||
|
data: searchData,
|
||||||
|
});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (locale) {
|
||||||
|
// i18n.changeLanguage(locale);
|
||||||
|
// }
|
||||||
|
// }, [locale, i18n]);
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('locale', locale, i18n.language);
|
||||||
|
if (locale && i18n.language !== locale) {
|
||||||
|
i18n.changeLanguage(locale);
|
||||||
|
}
|
||||||
|
}, [locale]);
|
||||||
|
return <SearchingView {...searchingParam} searchData={searchData} t={t} />;
|
||||||
|
}
|
||||||
@ -5,7 +5,7 @@ import searchService from '@/services/search-service';
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
|
|
||||||
interface CreateSearchProps {
|
interface CreateSearchProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -156,13 +156,13 @@ export const useDeleteSearch = () => {
|
|||||||
return { data, isError, deleteSearch };
|
return { data, isError, deleteSearch };
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IllmSettingProps {
|
export interface IllmSettingProps {
|
||||||
llm_id: string;
|
llm_id: string;
|
||||||
parameter: string;
|
parameter: string;
|
||||||
temperature: number;
|
temperature?: number;
|
||||||
top_p: number;
|
top_p?: number;
|
||||||
frequency_penalty: number;
|
frequency_penalty?: number;
|
||||||
presence_penalty: number;
|
presence_penalty?: number;
|
||||||
}
|
}
|
||||||
interface IllmSettingEnableProps {
|
interface IllmSettingEnableProps {
|
||||||
temperatureEnabled?: boolean;
|
temperatureEnabled?: boolean;
|
||||||
@ -204,14 +204,29 @@ interface SearchDetailResponse {
|
|||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFetchSearchDetail = () => {
|
export const useFetchSearchDetail = (tenantId?: string) => {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const shared_id = searchParams.get('shared_id');
|
||||||
|
const searchId = id || shared_id;
|
||||||
|
let param: { search_id: string | null; tenant_id?: string } = {
|
||||||
|
search_id: searchId,
|
||||||
|
};
|
||||||
|
if (shared_id) {
|
||||||
|
param = {
|
||||||
|
search_id: searchId,
|
||||||
|
tenant_id: tenantId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const fetchSearchDetailFunc = shared_id
|
||||||
|
? searchService.getSearchDetailShare
|
||||||
|
: searchService.getSearchDetail;
|
||||||
const { data, isLoading, isError } = useQuery<SearchDetailResponse, Error>({
|
const { data, isLoading, isError } = useQuery<SearchDetailResponse, Error>({
|
||||||
queryKey: ['searchDetail', id],
|
queryKey: ['searchDetail', searchId],
|
||||||
|
enabled: !shared_id || !!tenantId,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data: response } = await searchService.getSearchDetail({
|
const { data: response } = await fetchSearchDetailFunc(param);
|
||||||
search_id: id,
|
|
||||||
});
|
|
||||||
if (response.code !== 0) {
|
if (response.code !== 0) {
|
||||||
throw new Error(response.message || 'Failed to fetch search detail');
|
throw new Error(response.message || 'Failed to fetch search detail');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,7 +66,7 @@ export default function SearchList() {
|
|||||||
setSearchListParams({ ...searchParams, page, page_size: pageSize });
|
setSearchListParams({ ...searchParams, page, page_size: pageSize });
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="w-full h-full flex flex-col">
|
||||||
<div className="px-8 pt-8">
|
<div className="px-8 pt-8">
|
||||||
<ListFilterBar
|
<ListFilterBar
|
||||||
icon={
|
icon={
|
||||||
@ -89,18 +89,23 @@ export default function SearchList() {
|
|||||||
</Button>
|
</Button>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[84vh] overflow-auto px-8">
|
<div className="flex-1">
|
||||||
{list?.data.search_apps.map((x) => {
|
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[84vh] overflow-auto px-8">
|
||||||
return <SearchCard key={x.id} data={x}></SearchCard>;
|
{list?.data.search_apps.map((x) => {
|
||||||
})}
|
return <SearchCard key={x.id} data={x}></SearchCard>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{list?.data.total && (
|
{list?.data.total && list?.data.total > 0 && (
|
||||||
<RAGFlowPagination
|
<div className="px-8 mb-4">
|
||||||
{...pick(searchParams, 'current', 'pageSize')}
|
<RAGFlowPagination
|
||||||
total={list?.data.total}
|
{...pick(searchParams, 'current', 'pageSize')}
|
||||||
onChange={handlePageChange}
|
total={list?.data.total}
|
||||||
/>
|
onChange={handlePageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
open={openCreateModal}
|
open={openCreateModal}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export function SearchCard({ data }: IProps) {
|
|||||||
navigateToSearch(data?.id);
|
navigateToSearch(data?.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardContent className="p-4 flex gap-2 items-start group">
|
<CardContent className="p-4 flex gap-2 items-start group h-full">
|
||||||
<div className="flex justify-between mb-4">
|
<div className="flex justify-between mb-4">
|
||||||
<RAGFlowAvatar
|
<RAGFlowAvatar
|
||||||
className="w-[32px] h-[32px]"
|
className="w-[32px] h-[32px]"
|
||||||
@ -27,7 +27,7 @@ export function SearchCard({ data }: IProps) {
|
|||||||
name={data.name}
|
name={data.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1 flex-1">
|
<div className="flex flex-col justify-between gap-1 flex-1 h-full">
|
||||||
<section className="flex justify-between">
|
<section className="flex justify-between">
|
||||||
<div className="text-[20px] font-bold w-80% leading-5">
|
<div className="text-[20px] font-bold w-80% leading-5">
|
||||||
{data.name}
|
{data.name}
|
||||||
@ -37,22 +37,13 @@ export function SearchCard({ data }: IProps) {
|
|||||||
</SearchDropdown>
|
</SearchDropdown>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div>{data.description}</div>
|
<section className="flex flex-col gap-1 mt-1">
|
||||||
<section className="flex justify-between">
|
<div>{data.description}</div>
|
||||||
<div>
|
<div>
|
||||||
Search app
|
|
||||||
<p className="text-sm opacity-80">
|
<p className="text-sm opacity-80">
|
||||||
{formatDate(data.update_time)}
|
{formatDate(data.update_time)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="space-x-2 invisible group-hover:visible">
|
|
||||||
<Button variant="icon" size="icon" onClick={navigateToSearch}>
|
|
||||||
<ChevronRight className="h-6 w-6" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="icon" size="icon">
|
|
||||||
<Trash2 />
|
|
||||||
</Button>
|
|
||||||
</div> */}
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useFetchMindMap, useFetchRelatedQuestions } from '@/hooks/chat-hooks';
|
import { useFetchRelatedQuestions } from '@/hooks/chat-hooks';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import {
|
import {
|
||||||
useTestChunkAllRetrieval,
|
useTestChunkAllRetrieval,
|
||||||
@ -18,17 +18,23 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import {
|
||||||
|
useGetSharedSearchParams,
|
||||||
|
useSearchFetchMindMap,
|
||||||
|
} from '../next-search/hooks';
|
||||||
|
|
||||||
export const useSendQuestion = (kbIds: string[]) => {
|
export const useSendQuestion = (kbIds: string[], tenantId?: string) => {
|
||||||
|
const { sharedId } = useGetSharedSearchParams();
|
||||||
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
|
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
|
||||||
api.ask,
|
sharedId ? api.askShare : api.ask,
|
||||||
);
|
);
|
||||||
const { testChunk, loading } = useTestChunkRetrieval();
|
|
||||||
const { testChunkAll } = useTestChunkAllRetrieval();
|
const { testChunk, loading } = useTestChunkRetrieval(tenantId);
|
||||||
|
const { testChunkAll } = useTestChunkAllRetrieval(tenantId);
|
||||||
const [sendingLoading, setSendingLoading] = useState(false);
|
const [sendingLoading, setSendingLoading] = useState(false);
|
||||||
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
||||||
const { fetchRelatedQuestions, data: relatedQuestions } =
|
const { fetchRelatedQuestions, data: relatedQuestions } =
|
||||||
useFetchRelatedQuestions();
|
useFetchRelatedQuestions(tenantId);
|
||||||
const [searchStr, setSearchStr] = useState<string>('');
|
const [searchStr, setSearchStr] = useState<string>('');
|
||||||
const [isFirstRender, setIsFirstRender] = useState(true);
|
const [isFirstRender, setIsFirstRender] = useState(true);
|
||||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
const [selectedDocumentIds, setSelectedDocumentIds] = useState<string[]>([]);
|
||||||
@ -43,7 +49,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
|||||||
setIsFirstRender(false);
|
setIsFirstRender(false);
|
||||||
setCurrentAnswer({} as IAnswer);
|
setCurrentAnswer({} as IAnswer);
|
||||||
setSendingLoading(true);
|
setSendingLoading(true);
|
||||||
send({ kb_ids: kbIds, question: q });
|
send({ kb_ids: kbIds, question: q, tenantId });
|
||||||
testChunk({
|
testChunk({
|
||||||
kb_id: kbIds,
|
kb_id: kbIds,
|
||||||
highlight: true,
|
highlight: true,
|
||||||
@ -61,6 +67,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
|||||||
fetchRelatedQuestions,
|
fetchRelatedQuestions,
|
||||||
setPagination,
|
setPagination,
|
||||||
pagination.pageSize,
|
pagination.pageSize,
|
||||||
|
tenantId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -218,7 +225,7 @@ export const useShowMindMapDrawer = (kbIds: string[], question: string) => {
|
|||||||
fetchMindMap,
|
fetchMindMap,
|
||||||
data: mindMap,
|
data: mindMap,
|
||||||
loading: mindMapLoading,
|
loading: mindMapLoading,
|
||||||
} = useFetchMindMap();
|
} = useSearchFetchMindMap();
|
||||||
|
|
||||||
const handleShowModal = useCallback(() => {
|
const handleShowModal = useCallback(() => {
|
||||||
const searchParams = { question: trim(question), kb_ids: kbIds };
|
const searchParams = { question: trim(question), kb_ids: kbIds };
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export enum Routes {
|
|||||||
AgentList = '/agent-list',
|
AgentList = '/agent-list',
|
||||||
Searches = '/next-searches',
|
Searches = '/next-searches',
|
||||||
Search = '/next-search',
|
Search = '/next-search',
|
||||||
|
SearchShare = '/next-search/share',
|
||||||
Chats = '/next-chats',
|
Chats = '/next-chats',
|
||||||
Chat = '/next-chat',
|
Chat = '/next-chat',
|
||||||
Files = '/files',
|
Files = '/files',
|
||||||
@ -234,6 +235,11 @@ const routes = [
|
|||||||
layout: false,
|
layout: false,
|
||||||
component: `@/pages${Routes.Search}`,
|
component: `@/pages${Routes.Search}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.SearchShare}`,
|
||||||
|
layout: false,
|
||||||
|
component: `@/pages${Routes.SearchShare}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Agents,
|
path: Routes.Agents,
|
||||||
layout: false,
|
layout: false,
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const {
|
|||||||
listTagByKnowledgeIds,
|
listTagByKnowledgeIds,
|
||||||
setMeta,
|
setMeta,
|
||||||
getMeta,
|
getMeta,
|
||||||
|
retrievalTestShare,
|
||||||
} = api;
|
} = api;
|
||||||
|
|
||||||
const methods = {
|
const methods = {
|
||||||
@ -164,6 +165,10 @@ const methods = {
|
|||||||
url: getMeta,
|
url: getMeta,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
},
|
},
|
||||||
|
retrievalTestShare: {
|
||||||
|
url: retrievalTestShare,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const kbService = registerServer<keyof typeof methods>(methods, request);
|
const kbService = registerServer<keyof typeof methods>(methods, request);
|
||||||
|
|||||||
@ -8,6 +8,10 @@ const {
|
|||||||
deleteSearch,
|
deleteSearch,
|
||||||
getSearchDetail,
|
getSearchDetail,
|
||||||
updateSearchSetting,
|
updateSearchSetting,
|
||||||
|
askShare,
|
||||||
|
mindmapShare,
|
||||||
|
getRelatedQuestionsShare,
|
||||||
|
getSearchDetailShare,
|
||||||
} = api;
|
} = api;
|
||||||
const methods = {
|
const methods = {
|
||||||
createSearch: {
|
createSearch: {
|
||||||
@ -27,6 +31,23 @@ const methods = {
|
|||||||
url: updateSearchSetting,
|
url: updateSearchSetting,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
},
|
},
|
||||||
|
askShare: {
|
||||||
|
url: askShare,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
mindmapShare: {
|
||||||
|
url: mindmapShare,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
getRelatedQuestionsShare: {
|
||||||
|
url: getRelatedQuestionsShare,
|
||||||
|
method: 'post',
|
||||||
|
},
|
||||||
|
|
||||||
|
getSearchDetailShare: {
|
||||||
|
url: getSearchDetailShare,
|
||||||
|
method: 'get',
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
const searchService = registerServer<keyof typeof methods>(methods, request);
|
const searchService = registerServer<keyof typeof methods>(methods, request);
|
||||||
|
|
||||||
|
|||||||
@ -181,5 +181,10 @@ export default {
|
|||||||
getSearchList: `${api_host}/search/list`,
|
getSearchList: `${api_host}/search/list`,
|
||||||
deleteSearch: `${api_host}/search/rm`,
|
deleteSearch: `${api_host}/search/rm`,
|
||||||
getSearchDetail: `${api_host}/search/detail`,
|
getSearchDetail: `${api_host}/search/detail`,
|
||||||
|
getSearchDetailShare: `${ExternalApi}${api_host}/searchbots/detail`,
|
||||||
updateSearchSetting: `${api_host}/search/update`,
|
updateSearchSetting: `${api_host}/search/update`,
|
||||||
|
askShare: `${ExternalApi}${api_host}/searchbots/ask`,
|
||||||
|
mindmapShare: `${ExternalApi}${api_host}/searchbots/mindmap`,
|
||||||
|
getRelatedQuestionsShare: `${ExternalApi}${api_host}/searchbots/related_questions`,
|
||||||
|
retrievalTestShare: `${ExternalApi}${api_host}/searchbots/retrieval_test`,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user