mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-30 10:28:42 +00:00
perf: quote response
This commit is contained in:
191
client/src/components/ChatBox/QuoteModal.tsx
Normal file
191
client/src/components/ChatBox/QuoteModal.tsx
Normal file
@@ -0,0 +1,191 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalHeader,
|
||||
Box,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import InputDataModal from '@/pages/kb/detail/components/InputDataModal';
|
||||
import { getKbDataItemById } from '@/api/plugins/kb';
|
||||
import { useLoading } from '@/hooks/useLoading';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getHistoryQuote, updateHistoryQuote } from '@/api/chat';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { getErrText } from '@/utils/tools';
|
||||
import { QuoteItemType } from '@/pages/api/openapi/modules/kb/search';
|
||||
|
||||
const QuoteModal = ({
|
||||
historyId,
|
||||
contentId,
|
||||
rawSearch = [],
|
||||
onClose
|
||||
}: {
|
||||
historyId?: string;
|
||||
contentId?: string;
|
||||
rawSearch?: QuoteItemType[];
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { setIsLoading, Loading } = useLoading();
|
||||
const [editDataItem, setEditDataItem] = useState<{
|
||||
kbId: string;
|
||||
dataId: string;
|
||||
a: string;
|
||||
q: string;
|
||||
}>();
|
||||
|
||||
const {
|
||||
data: quote = [],
|
||||
refetch,
|
||||
isLoading
|
||||
} = useQuery(['getHistoryQuote'], () => {
|
||||
if (historyId && contentId) {
|
||||
return getHistoryQuote({ historyId, contentId });
|
||||
}
|
||||
if (rawSearch.length > 0) {
|
||||
return rawSearch;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
/**
|
||||
* update kbData, update mongo status and reload quotes
|
||||
*/
|
||||
const updateQuoteStatus = useCallback(
|
||||
async (quoteId: string, sourceText: string) => {
|
||||
if (!historyId || !contentId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await updateHistoryQuote({
|
||||
contentId,
|
||||
historyId,
|
||||
quoteId,
|
||||
sourceText
|
||||
});
|
||||
// reload quote
|
||||
refetch();
|
||||
} catch (err) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: getErrText(err)
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
},
|
||||
[contentId, historyId, refetch, setIsLoading, toast]
|
||||
);
|
||||
|
||||
/**
|
||||
* click edit, get new kbDataItem
|
||||
*/
|
||||
const onclickEdit = useCallback(
|
||||
async (item: QuoteItemType) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const data = (await getKbDataItemById(item.id)) as QuoteItemType;
|
||||
|
||||
if (!data) {
|
||||
updateQuoteStatus(item.id, '已删除');
|
||||
throw new Error('该数据已被删除');
|
||||
}
|
||||
|
||||
setEditDataItem({
|
||||
kbId: data.kb_id,
|
||||
dataId: data.id,
|
||||
q: data.q,
|
||||
a: data.a
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: getErrText(err)
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
},
|
||||
[setIsLoading, toast, updateQuoteStatus]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal isOpen={true} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
position={'relative'}
|
||||
maxW={'min(90vw, 700px)'}
|
||||
h={'80vh'}
|
||||
overflow={'overlay'}
|
||||
>
|
||||
<ModalHeader>
|
||||
知识库引用({quote.length}条)
|
||||
<Box fontSize={'sm'} fontWeight={'normal'}>
|
||||
注意: 修改知识库内容成功后,此处不会显示。点击编辑后,才是显示最新的内容。
|
||||
</Box>
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} fontSize={'sm'}>
|
||||
{quote.map((item) => (
|
||||
<Box
|
||||
key={item.id}
|
||||
flex={'1 0 0'}
|
||||
p={2}
|
||||
borderRadius={'sm'}
|
||||
border={theme.borders.base}
|
||||
_notLast={{ mb: 2 }}
|
||||
position={'relative'}
|
||||
_hover={{ '& .edit': { display: 'flex' } }}
|
||||
>
|
||||
{item.source && <Box color={'myGray.600'}>({item.source})</Box>}
|
||||
<Box>{item.q}</Box>
|
||||
<Box>{item.a}</Box>
|
||||
<Box
|
||||
className="edit"
|
||||
display={'none'}
|
||||
position={'absolute'}
|
||||
right={0}
|
||||
top={0}
|
||||
bottom={0}
|
||||
w={'40px'}
|
||||
bg={'rgba(255,255,255,0.9)'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
boxShadow={'-10px 0 10px rgba(255,255,255,1)'}
|
||||
>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
w={'18px'}
|
||||
h={'18px'}
|
||||
cursor={'pointer'}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
color: 'myBlue.700'
|
||||
}}
|
||||
onClick={() => onclickEdit(item)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</ModalBody>
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
{editDataItem && (
|
||||
<InputDataModal
|
||||
onClose={() => setEditDataItem(undefined)}
|
||||
onSuccess={() => updateQuoteStatus(editDataItem.dataId, '手动修改')}
|
||||
onDelete={() => updateQuoteStatus(editDataItem.dataId, '已删除')}
|
||||
kbId={editDataItem.kbId}
|
||||
defaultValues={editDataItem}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuoteModal;
|
@@ -15,7 +15,7 @@ import { Box, Card, Flex, Input, Textarea, Button, useTheme } from '@chakra-ui/r
|
||||
import { useUserStore } from '@/store/user';
|
||||
|
||||
import { Types } from 'mongoose';
|
||||
import { HUMAN_ICON } from '@/constants/chat';
|
||||
import { HUMAN_ICON, quoteLenKey, rawSearchKey } from '@/constants/chat';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
@@ -26,10 +26,15 @@ import { VariableInputEnum } from '@/constants/app';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MySelect from '@/components/Select';
|
||||
import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
|
||||
import styles from './index.module.scss';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import { fileDownload } from '@/utils/file';
|
||||
import { htmlTemplate } from '@/constants/common';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
||||
|
||||
import styles from './index.module.scss';
|
||||
import { QuoteItemType } from '@/pages/api/openapi/modules/kb/search';
|
||||
|
||||
const textareaMinH = '22px';
|
||||
export type StartChatFnProps = {
|
||||
@@ -65,6 +70,7 @@ const VariableLabel = ({
|
||||
|
||||
const ChatBox = (
|
||||
{
|
||||
historyId,
|
||||
appAvatar,
|
||||
variableModules,
|
||||
welcomeText,
|
||||
@@ -72,11 +78,14 @@ const ChatBox = (
|
||||
onStartChat,
|
||||
onDelMessage
|
||||
}: {
|
||||
historyId?: string;
|
||||
appAvatar: string;
|
||||
variableModules?: VariableItemType[];
|
||||
welcomeText?: string;
|
||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
||||
onStartChat: (e: StartChatFnProps) => Promise<{ responseText: string }>;
|
||||
onStartChat: (
|
||||
e: StartChatFnProps
|
||||
) => Promise<{ responseText?: string; rawSearch?: QuoteItemType[] }>;
|
||||
onDelMessage?: (e: { contentId?: string; index: number }) => void;
|
||||
},
|
||||
ref: ForwardedRef<ComponentRef>
|
||||
@@ -92,6 +101,10 @@ const ChatBox = (
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [variables, setVariables] = useState<Record<string, any>>({});
|
||||
const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]);
|
||||
const [quoteModalData, setQuoteModalData] = useState<{
|
||||
contentId?: string;
|
||||
rawSearch?: QuoteItemType[];
|
||||
}>();
|
||||
|
||||
const isChatting = useMemo(
|
||||
() => chatHistory[chatHistory.length - 1]?.status === 'loading',
|
||||
@@ -235,13 +248,25 @@ const ChatBox = (
|
||||
|
||||
const messages = adaptChatItem_openAI({ messages: newChatList, reserveId: true });
|
||||
|
||||
await onStartChat({
|
||||
const { rawSearch } = await onStartChat({
|
||||
messages,
|
||||
controller: abortSignal,
|
||||
generatingMessage,
|
||||
variables: data
|
||||
});
|
||||
|
||||
// set finish status
|
||||
setChatHistory((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish',
|
||||
rawSearch
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
generatingScroll();
|
||||
TextareaDom.current?.focus();
|
||||
@@ -258,18 +283,18 @@ const ChatBox = (
|
||||
resetInputVal(value);
|
||||
setChatHistory(newChatList.slice(0, newChatList.length - 2));
|
||||
}
|
||||
}
|
||||
|
||||
// set finish status
|
||||
setChatHistory((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish'
|
||||
};
|
||||
})
|
||||
);
|
||||
// set finish status
|
||||
setChatHistory((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish'
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
isChatting,
|
||||
@@ -439,7 +464,24 @@ const ChatBox = (
|
||||
source={item.value}
|
||||
isChatting={index === chatHistory.length - 1 && isChatting}
|
||||
/>
|
||||
{(item[quoteLenKey] || item[rawSearchKey]?.length) && (
|
||||
<Button
|
||||
size={'xs'}
|
||||
variant={'base'}
|
||||
mt={2}
|
||||
w={'80px'}
|
||||
onClick={() => {
|
||||
setQuoteModalData({
|
||||
contentId: item._id,
|
||||
rawSearch: item[rawSearchKey]
|
||||
});
|
||||
}}
|
||||
>
|
||||
{item[quoteLenKey] || item[rawSearchKey]?.length}条引用
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
<Flex {...controlContainerStyle}>
|
||||
<MyTooltip label={'复制'}>
|
||||
<MyIcon
|
||||
@@ -611,6 +653,15 @@ const ChatBox = (
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
{/* quote modal */}
|
||||
{!!quoteModalData && (
|
||||
<QuoteModal
|
||||
historyId={historyId}
|
||||
{...quoteModalData}
|
||||
onClose={() => setQuoteModalData(undefined)}
|
||||
/>
|
||||
)}
|
||||
{/* quote modal */}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user