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

@@ -3,6 +3,7 @@ import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { Types } from 'mongoose';
import { rawSearchKey } from '@/constants/chat';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -35,13 +36,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
{
$project: {
quote: '$content.quote'
[rawSearchKey]: `$content.${rawSearchKey}`
}
}
]);
jsonRes(res, {
data: history[0]?.quote || []
data: history[0]?.[rawSearchKey] || []
});
} catch (err) {
jsonRes(res, {

View File

@@ -11,7 +11,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
contentId,
quoteId,
sourceText = ''
} = req.query as {
} = req.body as {
historyId: string;
contentId: string;
quoteId: string;
@@ -33,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
{
$set: {
'content.$.quote.$[quoteElem].source': sourceText
'content.$.rawSearch.$[quoteElem].source': sourceText
}
},
{

View File

@@ -9,6 +9,7 @@ import mongoose from 'mongoose';
import type { AppSchema, ChatSchema } from '@/types/mongoSchema';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
import { quoteLenKey, rawSearchKey } from '@/constants/chat';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -82,8 +83,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
_id: '$content._id',
obj: '$content.obj',
value: '$content.value',
systemPrompt: '$content.systemPrompt',
quoteLen: { $size: { $ifNull: ['$content.quote', []] } }
[quoteLenKey]: { $size: { $ifNull: [`$content.${rawSearchKey}`, []] } }
}
}
]);

View File

@@ -128,7 +128,6 @@ export async function chatCompletion({
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
const chatAPI = getOpenAIApi();
console.log(adaptMessages);
/* count response max token */
const promptsToken = modelToolMap.countTokens({

View File

@@ -3,13 +3,14 @@ import { jsonRes } from '@/service/response';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import type { ChatItemType } from '@/types/chat';
import { ChatRoleEnum } from '@/constants/chat';
import { ChatRoleEnum, rawSearchKey } from '@/constants/chat';
import { modelToolMap } from '@/utils/plugin';
import { getVector } from '../../plugin/vector';
import { countModelPrice, pushTaskBillListItem } from '@/service/events/pushBill';
import { getModel } from '@/service/utils/data';
export type QuoteItemType = {
kb_id: string;
id: string;
q: string;
a: string;
@@ -26,7 +27,7 @@ type Props = {
billId?: string;
};
type Response = {
rawSearch: QuoteItemType[];
[rawSearchKey]: QuoteItemType[];
isEmpty?: boolean;
quotePrompt?: string;
};
@@ -85,7 +86,7 @@ export async function kbSearch({
PgClient.query(
`BEGIN;
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
select id,q,a,source from modelData where kb_id IN (${kb_ids
select kb_id,id,q,a,source from modelData where kb_id IN (${kb_ids
.map((item) => `'${item}'`)
.join(',')}) AND vector <#> '[${vectors[0]}]' < -${similarity} order by vector <#> '[${
vectors[0]

View File

@@ -42,11 +42,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
export function gpt_chatItemTokenSlice({
messages,
model,
model = 'gpt-3.5-turbo',
maxToken
}: {
messages: ChatItemType[];
model: ModelType;
model?: ModelType;
maxToken: number;
}) {
let result: ChatItemType[] = [];

View File

@@ -94,6 +94,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
getChatHistory({ historyId, userId })
]);
const isOwner = !shareId && userId === String(app.userId);
const prompts = history.concat(gptMessage2ChatType(messages));
if (prompts[prompts.length - 1].obj === 'AI') {
prompts.pop();
@@ -143,24 +145,30 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
_id: messages[messages.length - 1]._id,
obj: ChatRoleEnum.AI,
value: answerText,
responseData
...responseData
}
],
userId
});
}
console.log(`finish time: ${(Date.now() - startTime) / 100}s`);
if (stream) {
sseResponse({
res,
event: sseResponseEventEnum.answer,
data: '[DONE]'
});
sseResponse({
res,
event: sseResponseEventEnum.appStreamResponse,
data: JSON.stringify(responseData)
});
if (isOwner) {
sseResponse({
res,
event: sseResponseEventEnum.appStreamResponse,
data: JSON.stringify(responseData)
});
}
res.end();
} else {
res.json({
@@ -189,7 +197,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
delTaskBill(billId);
if (stream) {
res.status(500);
sseErrRes(res, err);
res.end();
} else {

View File

@@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const where: any = [['user_id', userId], 'AND', ['id', dataId]];
const searchRes = await PgClient.select<KbDataItemType>('modelData', {
fields: ['id', 'q', 'a', 'source'],
fields: ['kb_id', 'id', 'q', 'a', 'source'],
where,
limit: 1
});

View File

@@ -63,7 +63,7 @@ const ChatTest = (
const history = messages.slice(-historyMaxLen - 2, -2);
// 流请求,获取数据
const { responseText, errMsg } = await streamFetch({
const { responseText, rawSearch } = await streamFetch({
url: '/api/chat/chatTest',
data: {
history,
@@ -77,14 +77,7 @@ const ChatTest = (
abortSignal: controller
});
if (errMsg) {
return Promise.reject({
message: errMsg,
responseText
});
}
return { responseText };
return { responseText, rawSearch };
},
[app._id, app.name, modules]
);

View File

@@ -1,177 +0,0 @@
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';
const QuoteModal = ({
historyId,
contentId,
onClose
}: {
historyId: string;
contentId: string;
onClose: () => void;
}) => {
const theme = useTheme();
const { toast } = useToast();
const { setIsLoading, Loading } = useLoading();
const [editDataItem, setEditDataItem] = useState<{
dataId: string;
a: string;
q: string;
}>();
const {
data: quote = [],
refetch,
isLoading
} = useQuery(['getHistoryQuote'], () => getHistoryQuote({ historyId, contentId }));
/**
* update kbData, update mongo status and reload quotes
*/
const updateQuoteStatus = useCallback(
async (quoteId: string, sourceText: string) => {
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({
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=""
defaultValues={editDataItem}
/>
)}
</>
);
};
export default QuoteModal;

View File

@@ -1,28 +1,18 @@
import React, { useCallback, useState, useRef } from 'react';
import React, { useCallback, useRef } from 'react';
import { useRouter } from 'next/router';
import { getInitChatSiteInfo, delChatRecordByIndex, putChatHistory } from '@/api/chat';
import {
Box,
Flex,
useColorModeValue,
Modal,
ModalOverlay,
ModalContent,
ModalBody,
ModalCloseButton,
ModalHeader,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import { useToast } from '@/hooks/useToast';
import { useGlobalStore } from '@/store/global';
import { useQuery } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { streamFetch } from '@/api/fetch';
import MyIcon from '@/components/Icon';
import { useChatStore } from '@/store/chat';
import { useLoading } from '@/hooks/useLoading';
@@ -32,8 +22,6 @@ import PageContainer from '@/components/PageContainer';
import SideBar from '@/components/SideBar';
import ChatHistorySlider from './components/ChatHistorySlider';
import SliderApps from './components/SliderApps';
import Tag from '@/components/Tag';
import ToolMenu from './components/ToolMenu';
import ChatHeader from './components/ChatHeader';
const Chat = () => {
@@ -44,9 +32,6 @@ const Chat = () => {
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const [showHistoryQuote, setShowHistoryQuote] = useState<string>();
const [showSystemPrompt, setShowSystemPrompt] = useState('');
const {
lastChatAppId,
setLastChatAppId,
@@ -67,7 +52,7 @@ const Chat = () => {
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const prompts = messages.slice(-2);
const { responseText, newHistoryId } = await streamFetch({
const { responseText, newHistoryId, rawSearch } = await streamFetch({
data: {
messages: prompts,
variables,
@@ -113,7 +98,7 @@ const Chat = () => {
history: ChatBoxRef.current?.getChatHistory() || state.history
}));
return { responseText };
return { responseText, rawSearch };
},
[appId, history, historyId, router, setChatData, updateHistory]
);
@@ -297,6 +282,7 @@ const Chat = () => {
<Box flex={1}>
<ChatBox
ref={ChatBoxRef}
historyId={historyId}
appAvatar={chatData.app.avatar}
variableModules={chatData.app.variableModules}
welcomeText={chatData.app.welcomeText}
@@ -311,27 +297,6 @@ const Chat = () => {
</Flex>
<Loading fixed={false} />
</PageContainer>
{/* quote modal*/}
{/* {showHistoryQuote && historyId && (
<QuoteModal
historyId={historyId}
onClose={() => setShowHistoryQuote(undefined)}
/>
)} */}
{/* system prompt show modal */}
{
<Modal isOpen={!!showSystemPrompt} onClose={() => setShowSystemPrompt('')}>
<ModalOverlay />
<ModalContent maxW={'min(90vw, 600px)'} maxH={'80vh'} minH={'50vh'} overflow={'overlay'}>
<ModalCloseButton />
<ModalHeader></ModalHeader>
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} fontSize={'xs'}>
{showSystemPrompt}
</ModalBody>
</ModalContent>
</Modal>
}
</Flex>
);
};

View File

@@ -184,7 +184,7 @@ const ShareChat = ({ shareId, historyId }: { shareId: string; historyId: string
{/* header */}
<ChatHeader
appAvatar={shareChatData.app.avatar}
appName={shareChatData.history.title}
appName={shareChatData.app.name}
history={shareChatData.history.chats}
onOpenSlider={onOpenSlider}
/>