feat: Fetch mind map in search page #2247 (#2292)

### What problem does this PR solve?
feat: Fetch mind map in search page #2247

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2024-09-06 19:56:17 +08:00 committed by GitHub
parent 1aba978de2
commit e85fea31a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 111 additions and 133 deletions

View File

@ -16,7 +16,7 @@ import {
} from '@antv/g6'; } from '@antv/g6';
import { TreeData } from '@antv/g6/lib/types'; import { TreeData } from '@antv/g6/lib/types';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { useCallback, useEffect, useRef } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
const rootId = 'root'; const rootId = 'root';
@ -294,9 +294,10 @@ register(
interface IProps { interface IProps {
data: TreeData; data: TreeData;
show: boolean; show: boolean;
style?: React.CSSProperties;
} }
const IndentedTree = ({ data, show }: IProps) => { const IndentedTree = ({ data, show, style = {} }: IProps) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const graphRef = useRef<Graph | null>(null); const graphRef = useRef<Graph | null>(null);
@ -388,6 +389,7 @@ const IndentedTree = ({ data, show }: IProps) => {
width: '90vw', width: '90vw',
height: '80vh', height: '80vh',
display: show ? 'block' : 'none', display: show ? 'block' : 'none',
...style,
}} }}
/> />
); );

View File

@ -490,13 +490,32 @@ export const useFetchMindMap = () => {
mutateAsync, mutateAsync,
} = useMutation({ } = useMutation({
mutationKey: ['fetchMindMap'], mutationKey: ['fetchMindMap'],
gcTime: 0,
mutationFn: async (params: IAskRequestBody) => { mutationFn: async (params: IAskRequestBody) => {
const { data } = await chatService.getMindMap(params); const { data } = await chatService.getMindMap(params);
return data; return data?.data ?? [];
}, },
}); });
return { data, loading, fetchMindMap: mutateAsync }; return { data, loading, fetchMindMap: mutateAsync };
}; };
export const useFetchRelatedQuestions = () => {
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['fetchRelatedQuestions'],
gcTime: 0,
mutationFn: async (question: string): Promise<string[]> => {
const { data } = await chatService.getRelatedQuestions({ question });
return data?.data ?? [];
},
});
return { data, loading, fetchRelatedQuestions: mutateAsync };
};
//#endregion //#endregion

View File

@ -57,7 +57,7 @@ export const useFetchFlowTemplates = (): ResponseType<IFlowTemplate[]> => {
data.data.unshift({ data.data.unshift({
id: uuid(), id: uuid(),
title: 'Blank', title: 'Blank',
description: 'Create from nothing', description: 'Create your agent from scratch',
dsl: EmptyDsl, dsl: EmptyDsl,
}); });
} }

View File

@ -5,6 +5,6 @@ export interface IFeedbackRequestBody {
} }
export interface IAskRequestBody { export interface IAskRequestBody {
questionkb_ids: string; question: string;
kb_ids: string[]; kb_ids: string[];
} }

View File

@ -9,7 +9,7 @@ import { useLocation } from 'umi';
import Toolbar from '../right-toolbar'; import Toolbar from '../right-toolbar';
import { useFetchAppConf } from '@/hooks/logic-hooks'; import { useFetchAppConf } from '@/hooks/logic-hooks';
import { MessageOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import styles from './index.less'; import styles from './index.less';
const { Header } = Layout; const { Header } = Layout;
@ -26,8 +26,8 @@ const RagHeader = () => {
const tagsData = useMemo( const tagsData = useMemo(
() => [ () => [
{ path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon }, { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon },
{ path: '/chat', name: t('chat'), icon: MessageOutlined }, // { path: '/chat', name: t('chat'), icon: MessageOutlined },
// { path: '/search', name: t('search'), icon: SearchOutlined }, { path: '/search', name: t('search'), icon: SearchOutlined },
{ path: '/flow', name: t('flow'), icon: GraphIcon }, { path: '/flow', name: t('flow'), icon: GraphIcon },
{ path: '/file', name: t('fileManager'), icon: FileIcon }, { path: '/file', name: t('fileManager'), icon: FileIcon },
], ],

View File

@ -634,6 +634,7 @@ The above is the content you need to summarize.`,
messagePlaceholder: 'message', messagePlaceholder: 'message',
messageMsg: 'Please input message or delete this field.', messageMsg: 'Please input message or delete this field.',
addField: 'Add field', addField: 'Add field',
addMessage: 'Add message',
loop: 'Loop', loop: 'Loop',
loopTip: loopTip:
'Loop is the upper limit of the number of loops of the current component, when the number of loops exceeds the value of loop, it means that the component can not complete the current task, please re-optimize agent', 'Loop is the upper limit of the number of loops of the current component, when the number of loops exceeds the value of loop, it means that the component can not complete the current task, please re-optimize agent',
@ -672,7 +673,7 @@ The above is the content you need to summarize.`,
begin: 'Begin', begin: 'Begin',
message: 'Message', message: 'Message',
blank: 'Blank', blank: 'Blank',
createFromNothing: 'Create from nothing', createFromNothing: 'Create your agent from scratch',
addItem: 'Add Item', addItem: 'Add Item',
addSubItem: 'Add Sub Item', addSubItem: 'Add Sub Item',
nameRequiredMsg: 'Name is required', nameRequiredMsg: 'Name is required',

View File

@ -590,6 +590,7 @@ export default {
messagePlaceholder: '訊息', messagePlaceholder: '訊息',
messageMsg: '請輸入訊息或刪除此欄位。', messageMsg: '請輸入訊息或刪除此欄位。',
addField: '新增字段', addField: '新增字段',
addMessage: '新增訊息',
loop: '循環上限', loop: '循環上限',
loopTip: loopTip:
'loop為目前元件循環次數上限當循環次數超過loop的值時表示元件無法完成目前任務請重新最佳化agent', 'loop為目前元件循環次數上限當循環次數超過loop的值時表示元件無法完成目前任務請重新最佳化agent',

View File

@ -609,6 +609,7 @@ export default {
messagePlaceholder: '消息', messagePlaceholder: '消息',
messageMsg: '请输入消息或删除此字段。', messageMsg: '请输入消息或删除此字段。',
addField: '新增字段', addField: '新增字段',
addMessage: '新增消息',
loop: '循环上限', loop: '循环上限',
loopTip: loopTip:
'loop为当前组件循环次数上限当循环次数超过loop的值时说明组件不能完成当前任务请重新优化agent', 'loop为当前组件循环次数上限当循环次数超过loop的值时说明组件不能完成当前任务请重新优化agent',

View File

@ -90,7 +90,7 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
label={t('selfRag')} label={t('selfRag')}
valuePropName="checked" valuePropName="checked"
name={['prompt_config', 'self_rag']} name={['prompt_config', 'self_rag']}
@ -98,7 +98,7 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
initialValue={false} initialValue={false}
> >
<Switch /> <Switch />
</Form.Item> </Form.Item> */}
{/* <Form.Item {/* <Form.Item
label={t('tts')} label={t('tts')}
valuePropName="checked" valuePropName="checked"

View File

@ -74,7 +74,7 @@ const MessageForm = ({ onValuesChange, form }: IOperatorForm) => {
style={{ width: '80%' }} style={{ width: '80%' }}
icon={<PlusOutlined />} icon={<PlusOutlined />}
> >
{t('addField')} {t('addMessage')}
</Button> </Button>
</Form.Item> </Form.Item>
</> </>

View File

@ -1,112 +1,3 @@
import { Graph } from '@antv/g6';
import { useSize } from 'ahooks';
import { useEffect, useRef } from 'react';
import { graphData } from './constant';
import InputWithUpload from './input-upload'; import InputWithUpload from './input-upload';
import styles from './index.less';
import { Converter } from './util';
const converter = new Converter();
const nextData = converter.buildNodesAndCombos(
graphData.nodes,
graphData.edges,
);
console.log('🚀 ~ nextData:', nextData);
const finalData = { ...graphData, ...nextData };
const ForceGraph = () => {
const containerRef = useRef<HTMLDivElement>(null);
const size = useSize(containerRef);
let graph: Graph;
const render = () => {
graph = new Graph({
container: containerRef.current!,
autoFit: 'view',
behaviors: [
'drag-element',
'drag-canvas',
'zoom-canvas',
'collapse-expand',
{
type: 'hover-activate',
degree: 1, // 👈🏻 Activate relations.
},
],
plugins: [
{
type: 'tooltip',
getContent: (e, items) => {
if (items.every((x) => x?.description)) {
let result = ``;
items.forEach((item) => {
if (item?.description) {
result += `<p>${item?.description}</p>`;
}
});
return result;
}
return undefined;
},
},
],
layout: {
type: 'combo-combined',
preventOverlap: true,
comboPadding: 1,
spacing: 20,
},
node: {
style: {
size: 20,
labelText: (d) => d.id,
labelPadding: 30,
// labelOffsetX: 20,
// labelOffsetY: 5,
labelPlacement: 'center',
labelWordWrap: true,
},
palette: {
type: 'group',
field: (d) => d.combo,
},
// state: {
// highlight: {
// fill: '#D580FF',
// halo: true,
// lineWidth: 0,
// },
// dim: {
// fill: '#99ADD1',
// },
// },
},
edge: {
style: (model) => {
const { size, color } = model.data;
return {
stroke: color || '#99ADD1',
lineWidth: size || 1,
};
},
},
// data: graphData,
});
graph.setData(finalData);
graph.render();
};
useEffect(() => {
console.info('rendered');
render();
}, []);
return <div ref={containerRef} className={styles.container} />;
};
export default InputWithUpload; export default InputWithUpload;

View File

@ -1,3 +1,4 @@
import { useFetchMindMap, useFetchRelatedQuestions } from '@/hooks/chat-hooks';
import { useTestChunkRetrieval } from '@/hooks/knowledge-hooks'; import { useTestChunkRetrieval } from '@/hooks/knowledge-hooks';
import { useSendMessageWithSse } from '@/hooks/logic-hooks'; import { useSendMessageWithSse } from '@/hooks/logic-hooks';
import { IAnswer } from '@/interfaces/database/chat'; import { IAnswer } from '@/interfaces/database/chat';
@ -10,6 +11,13 @@ export const useSendQuestion = (kbIds: string[]) => {
const { testChunk, loading } = useTestChunkRetrieval(); const { testChunk, loading } = useTestChunkRetrieval();
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 } =
useFetchRelatedQuestions();
const {
fetchMindMap,
data: mindMap,
loading: mindMapLoading,
} = useFetchMindMap();
const sendQuestion = useCallback( const sendQuestion = useCallback(
(question: string) => { (question: string) => {
@ -17,8 +25,13 @@ export const useSendQuestion = (kbIds: string[]) => {
setSendingLoading(true); setSendingLoading(true);
send({ kb_ids: kbIds, question }); send({ kb_ids: kbIds, question });
testChunk({ kb_id: kbIds, highlight: true, question }); testChunk({ kb_id: kbIds, highlight: true, question });
fetchMindMap({
question,
kb_ids: kbIds,
});
fetchRelatedQuestions(question);
}, },
[send, testChunk, kbIds], [send, testChunk, kbIds, fetchRelatedQuestions, fetchMindMap],
); );
useEffect(() => { useEffect(() => {
@ -33,5 +46,13 @@ export const useSendQuestion = (kbIds: string[]) => {
} }
}, [done]); }, [done]);
return { sendQuestion, loading, sendingLoading, answer: currentAnswer }; return {
sendQuestion,
loading,
sendingLoading,
answer: currentAnswer,
relatedQuestions: relatedQuestions?.slice(0, 5) ?? [],
mindMap,
mindMapLoading,
};
}; };

View File

@ -2,6 +2,10 @@
.card { .card {
width: 100%; width: 100%;
} }
.tag {
padding: 4px 8px;
font-size: 14px;
}
} }
.searchSide { .searchSide {
@ -49,6 +53,6 @@
.graph { .graph {
width: 40%; width: 40%;
background-color: bisque; padding-right: 10px;
} }
} }

View File

@ -2,12 +2,13 @@ import HightLightMarkdown from '@/components/highlight-markdown';
import { ImageWithPopover } from '@/components/image'; import { ImageWithPopover } from '@/components/image';
import { useSelectTestingResult } from '@/hooks/knowledge-hooks'; import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
import { IReference } from '@/interfaces/database/chat'; import { IReference } from '@/interfaces/database/chat';
import { Card, Flex, Input, Layout, List, Space } from 'antd'; import { Card, Flex, Input, Layout, List, Skeleton, Space, Tag } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import MarkdownContent from '../chat/markdown-content'; import MarkdownContent from '../chat/markdown-content';
import { useSendQuestion } from './hooks'; import { useSendQuestion } from './hooks';
import SearchSidebar from './sidebar'; import SearchSidebar from './sidebar';
import IndentedTree from '@/components/indented-tree/indented-tree';
import styles from './index.less'; import styles from './index.less';
const { Content } = Layout; const { Content } = Layout;
@ -16,7 +17,14 @@ const { Search } = Input;
const SearchPage = () => { const SearchPage = () => {
const [checkedList, setCheckedList] = useState<string[]>([]); const [checkedList, setCheckedList] = useState<string[]>([]);
const list = useSelectTestingResult(); const list = useSelectTestingResult();
const { sendQuestion, answer, sendingLoading } = useSendQuestion(checkedList); const {
sendQuestion,
answer,
sendingLoading,
relatedQuestions,
mindMap,
mindMapLoading,
} = useSendQuestion(checkedList);
return ( return (
<Layout className={styles.searchPage}> <Layout className={styles.searchPage}>
@ -56,8 +64,29 @@ const SearchPage = () => {
</List.Item> </List.Item>
)} )}
/> />
{relatedQuestions?.length > 0 && (
<Card>
<Flex wrap="wrap" gap={'10px 0'}>
{relatedQuestions?.map((x, idx) => (
<Tag key={idx} className={styles.tag}>
{x}
</Tag>
))}
</Flex>
</Card>
)}
</section>
<section className={styles.graph}>
{mindMapLoading ? (
<Skeleton active />
) : (
<IndentedTree
data={mindMap}
show
style={{ width: '100%', height: '100%' }}
></IndentedTree>
)}
</section> </section>
<section className={styles.graph}></section>
</Flex> </Flex>
</Content> </Content>
</Layout> </Layout>

View File

@ -30,20 +30,23 @@ const SearchSidebar = ({ checkedList, setCheckedList }: IProps) => {
const indeterminate = const indeterminate =
checkedList.length > 0 && checkedList.length < list.length; checkedList.length > 0 && checkedList.length < list.length;
const onChange = useCallback((list: CheckboxValueType[]) => { const onChange = useCallback(
setCheckedList(list as string[]); (list: CheckboxValueType[]) => {
}, []); setCheckedList(list as string[]);
},
[setCheckedList],
);
const onCheckAllChange: CheckboxProps['onChange'] = useCallback( const onCheckAllChange: CheckboxProps['onChange'] = useCallback(
(e: CheckboxChangeEvent) => { (e: CheckboxChangeEvent) => {
setCheckedList(e.target.checked ? ids : []); setCheckedList(e.target.checked ? ids : []);
}, },
[ids], [ids, setCheckedList],
); );
useEffect(() => { useEffect(() => {
setCheckedList(ids); setCheckedList(ids);
}, [ids]); }, [ids, setCheckedList]);
return ( return (
<Sider className={styles.searchSide} theme={'light'} width={240}> <Sider className={styles.searchSide} theme={'light'} width={240}>
@ -53,7 +56,7 @@ const SearchSidebar = ({ checkedList, setCheckedList }: IProps) => {
onChange={onCheckAllChange} onChange={onCheckAllChange}
checked={checkAll} checked={checkAll}
> >
Check all All
</Checkbox> </Checkbox>
<Checkbox.Group <Checkbox.Group
className={styles.checkGroup} className={styles.checkGroup}

View File

@ -25,6 +25,7 @@ const {
tts, tts,
ask, ask,
mindmap, mindmap,
getRelatedQuestions,
} = api; } = api;
const methods = { const methods = {
@ -116,6 +117,10 @@ const methods = {
url: mindmap, url: mindmap,
method: 'post', method: 'post',
}, },
getRelatedQuestions: {
url: getRelatedQuestions,
method: 'post',
},
} as const; } as const;
const chatService = registerServer<keyof typeof methods>(methods, request); const chatService = registerServer<keyof typeof methods>(methods, request);

View File

@ -68,6 +68,7 @@ export default {
tts: `${api_host}/conversation/tts`, tts: `${api_host}/conversation/tts`,
ask: `${api_host}/conversation/ask`, ask: `${api_host}/conversation/ask`,
mindmap: `${api_host}/conversation/mindmap`, mindmap: `${api_host}/conversation/mindmap`,
getRelatedQuestions: `${api_host}/conversation/related_questions`,
// chat for external // chat for external
createToken: `${api_host}/api/new_token`, createToken: `${api_host}/api/new_token`,
listToken: `${api_host}/api/token_list`, listToken: `${api_host}/api/token_list`,