mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
perf: quote response
This commit is contained in:
@@ -4,8 +4,7 @@ import type { InitChatResponse, InitShareChatResponse } from './response/chat';
|
||||
import { RequestPaging } from '../types/index';
|
||||
import type { ShareChatSchema } from '@/types/mongoSchema';
|
||||
import type { ShareChatEditType } from '@/types/app';
|
||||
import { Obj2Query } from '@/utils/tools';
|
||||
import type { QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
|
||||
import type { QuoteItemType } from '@/pages/api/openapi/modules/kb/search';
|
||||
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
|
||||
|
||||
/**
|
||||
@@ -39,7 +38,7 @@ export const updateHistoryQuote = (params: {
|
||||
contentId: string;
|
||||
quoteId: string;
|
||||
sourceText: string;
|
||||
}) => GET(`/chat/history/updateHistoryQuote`, params);
|
||||
}) => PUT(`/chat/history/updateHistoryQuote`, params);
|
||||
|
||||
/**
|
||||
* 删除一句对话
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { sseResponseEventEnum } from '@/constants/chat';
|
||||
import { getErrText } from '@/utils/tools';
|
||||
import { parseStreamChunk } from '@/utils/adapt';
|
||||
import { QuoteItemType } from '@/pages/api/openapi/modules/kb/search';
|
||||
import { rawSearchKey } from '@/constants/chat';
|
||||
|
||||
interface StreamFetchProps {
|
||||
url?: string;
|
||||
@@ -14,88 +16,94 @@ export const streamFetch = ({
|
||||
onMessage,
|
||||
abortSignal
|
||||
}: StreamFetchProps) =>
|
||||
new Promise<{ responseText: string; errMsg: string; newHistoryId: string | null }>(
|
||||
async (resolve, reject) => {
|
||||
try {
|
||||
const response = await window.fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
signal: abortSignal.signal,
|
||||
body: JSON.stringify({
|
||||
...data,
|
||||
stream: true
|
||||
})
|
||||
});
|
||||
new Promise<{
|
||||
responseText: string;
|
||||
errMsg: string;
|
||||
newHistoryId: string | null;
|
||||
[rawSearchKey]: QuoteItemType[];
|
||||
}>(async (resolve, reject) => {
|
||||
try {
|
||||
const response = await window.fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
signal: abortSignal.signal,
|
||||
body: JSON.stringify({
|
||||
...data,
|
||||
stream: true
|
||||
})
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
const err = await response.json();
|
||||
return reject(err);
|
||||
}
|
||||
if (!response?.body) {
|
||||
throw new Error('Request Error');
|
||||
}
|
||||
|
||||
if (!response?.body) {
|
||||
throw new Error('Request Error');
|
||||
}
|
||||
const reader = response.body?.getReader();
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
// response data
|
||||
let responseText = '';
|
||||
let rawSearch: QuoteItemType[] = [];
|
||||
let errMsg = '';
|
||||
const newHistoryId = response.headers.get('newHistoryId');
|
||||
|
||||
// response data
|
||||
let responseText = '';
|
||||
let errMsg = '';
|
||||
const newHistoryId = response.headers.get('newHistoryId');
|
||||
|
||||
const read = async () => {
|
||||
try {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
if (response.status === 200) {
|
||||
return resolve({
|
||||
responseText,
|
||||
errMsg,
|
||||
newHistoryId
|
||||
});
|
||||
} else {
|
||||
return reject('响应过程出现异常~');
|
||||
}
|
||||
}
|
||||
const chunkResponse = parseStreamChunk(value);
|
||||
|
||||
chunkResponse.forEach((item) => {
|
||||
// parse json data
|
||||
const data = (() => {
|
||||
try {
|
||||
return JSON.parse(item.data);
|
||||
} catch (error) {
|
||||
return item.data;
|
||||
}
|
||||
})();
|
||||
|
||||
if (item.event === sseResponseEventEnum.answer && data !== '[DONE]') {
|
||||
const answer: string = data?.choices?.[0].delta.content || '';
|
||||
onMessage(answer);
|
||||
responseText += answer;
|
||||
} else if (item.event === sseResponseEventEnum.error) {
|
||||
errMsg = getErrText(data, '流响应错误');
|
||||
}
|
||||
});
|
||||
read();
|
||||
} catch (err: any) {
|
||||
if (err?.message === 'The user aborted a request.') {
|
||||
const read = async () => {
|
||||
try {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
if (response.status === 200 && !errMsg) {
|
||||
return resolve({
|
||||
responseText,
|
||||
errMsg,
|
||||
newHistoryId
|
||||
newHistoryId,
|
||||
rawSearch
|
||||
});
|
||||
} else {
|
||||
return reject({
|
||||
message: errMsg || '响应过程出现异常~',
|
||||
responseText
|
||||
});
|
||||
}
|
||||
reject(getErrText(err, '请求异常'));
|
||||
}
|
||||
};
|
||||
read();
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
const chunkResponse = parseStreamChunk(value);
|
||||
|
||||
reject(getErrText(err, '请求异常'));
|
||||
}
|
||||
chunkResponse.forEach((item) => {
|
||||
// parse json data
|
||||
const data = (() => {
|
||||
try {
|
||||
return JSON.parse(item.data);
|
||||
} catch (error) {
|
||||
return item.data;
|
||||
}
|
||||
})();
|
||||
|
||||
if (item.event === sseResponseEventEnum.answer && data !== '[DONE]') {
|
||||
const answer: string = data?.choices?.[0].delta.content || '';
|
||||
onMessage(answer);
|
||||
responseText += answer;
|
||||
} else if (item.event === sseResponseEventEnum.appStreamResponse) {
|
||||
rawSearch = data?.[rawSearchKey] ? data[rawSearchKey] : rawSearch;
|
||||
} else if (item.event === sseResponseEventEnum.error) {
|
||||
errMsg = getErrText(data, '流响应错误');
|
||||
}
|
||||
});
|
||||
read();
|
||||
} catch (err: any) {
|
||||
if (err?.message === 'The user aborted a request.') {
|
||||
return resolve({
|
||||
responseText,
|
||||
errMsg,
|
||||
newHistoryId,
|
||||
rawSearch
|
||||
});
|
||||
}
|
||||
reject(getErrText(err, '请求异常'));
|
||||
}
|
||||
};
|
||||
read();
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
|
||||
reject(getErrText(err, '请求异常'));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@@ -17,20 +17,24 @@ 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;
|
||||
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;
|
||||
@@ -40,13 +44,22 @@ const QuoteModal = ({
|
||||
data: quote = [],
|
||||
refetch,
|
||||
isLoading
|
||||
} = useQuery(['getHistoryQuote'], () => getHistoryQuote({ historyId, contentId }));
|
||||
} = 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({
|
||||
@@ -83,6 +96,7 @@ const QuoteModal = ({
|
||||
}
|
||||
|
||||
setEditDataItem({
|
||||
kbId: data.kb_id,
|
||||
dataId: data.id,
|
||||
q: data.q,
|
||||
a: data.a
|
||||
@@ -166,7 +180,7 @@ const QuoteModal = ({
|
||||
onClose={() => setEditDataItem(undefined)}
|
||||
onSuccess={() => updateQuoteStatus(editDataItem.dataId, '手动修改')}
|
||||
onDelete={() => updateQuoteStatus(editDataItem.dataId, '已删除')}
|
||||
kbId=""
|
||||
kbId={editDataItem.kbId}
|
||||
defaultValues={editDataItem}
|
||||
/>
|
||||
)}
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import type { AppItemType } from '@/types/app';
|
||||
import { rawSearchKey } from './chat';
|
||||
|
||||
/* app */
|
||||
export enum AppModuleItemTypeEnum {
|
||||
@@ -530,7 +531,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'rawSearch',
|
||||
key: rawSearchKey,
|
||||
label: '源搜索数据',
|
||||
type: 'hidden',
|
||||
response: true,
|
||||
@@ -1165,7 +1166,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'rawSearch',
|
||||
key: rawSearchKey,
|
||||
label: '源搜索数据',
|
||||
type: 'hidden',
|
||||
response: true,
|
||||
|
@@ -24,5 +24,8 @@ export const ChatRoleMap = {
|
||||
}
|
||||
};
|
||||
|
||||
export const rawSearchKey = 'rawSearch';
|
||||
export const quoteLenKey = 'quoteLen';
|
||||
|
||||
export const HUMAN_ICON = `https://fastgpt.run/icon/human.png`;
|
||||
export const LOGO_ICON = `https://fastgpt.run/icon/logo.png`;
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
Input_Template_TFSwitch,
|
||||
Input_Template_UserChatInput
|
||||
} from './inputTemplate';
|
||||
import { rawSearchKey } from '../chat';
|
||||
|
||||
export const VariableInputModule: AppModuleTemplateItemType = {
|
||||
logo: '/imgs/module/userGuide.png',
|
||||
@@ -215,7 +216,7 @@ export const KBSearchModule: AppModuleTemplateItemType = {
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'rawSearch',
|
||||
key: rawSearchKey,
|
||||
label: '源搜索数据',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
response: true,
|
||||
|
@@ -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, {
|
||||
|
@@ -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
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@@ -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}`, []] } }
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
@@ -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({
|
||||
|
@@ -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]
|
||||
|
@@ -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[] = [];
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
});
|
||||
|
@@ -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]
|
||||
);
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
@@ -44,20 +44,17 @@ const ChatSchema = new Schema({
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
quote: {
|
||||
rawSearch: {
|
||||
type: [
|
||||
{
|
||||
id: String,
|
||||
q: String,
|
||||
a: String,
|
||||
kb_id: String,
|
||||
source: String
|
||||
}
|
||||
],
|
||||
default: []
|
||||
},
|
||||
systemPrompt: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
],
|
||||
|
4
client/src/types/chat.d.ts
vendored
4
client/src/types/chat.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { ChatRoleEnum } from '@/constants/chat';
|
||||
import { ChatRoleEnum, rawSearchKey } from '@/constants/chat';
|
||||
import type { InitChatResponse, InitShareChatResponse } from '@/api/response/chat';
|
||||
import { QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
|
||||
|
||||
@@ -8,6 +8,8 @@ export type ChatItemType = {
|
||||
_id?: string;
|
||||
obj: `${ChatRoleEnum}`;
|
||||
value: string;
|
||||
[rawSearchKey]?: QuoteItemType[];
|
||||
quoteLen?: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user