perf: quote response

This commit is contained in:
archer
2023-07-17 16:12:51 +08:00
parent 60a9dfb55f
commit 53a4d9db05
20 changed files with 215 additions and 173 deletions

View 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;

View File

@@ -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>
);
};