mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-30 18:48:55 +00:00
Add SSE controller; fix share page login failed (#330)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fastgpt",
|
"name": "fastgpt",
|
||||||
"version": "4.4.3",
|
"version": "4.4.4",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
@@ -148,7 +148,7 @@ const ChatBox = (
|
|||||||
onDelMessage
|
onDelMessage
|
||||||
}: {
|
}: {
|
||||||
feedbackType?: `${FeedbackTypeEnum}`;
|
feedbackType?: `${FeedbackTypeEnum}`;
|
||||||
showMarkIcon?: boolean;
|
showMarkIcon?: boolean; // admin mark dataset
|
||||||
showVoiceIcon?: boolean;
|
showVoiceIcon?: boolean;
|
||||||
showEmptyIntro?: boolean;
|
showEmptyIntro?: boolean;
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
@@ -676,7 +676,11 @@ const ChatBox = (
|
|||||||
<>
|
<>
|
||||||
<Flex w={'100%'} alignItems={'flex-end'}>
|
<Flex w={'100%'} alignItems={'flex-end'}>
|
||||||
<ChatAvatar src={appAvatar} type={'AI'} />
|
<ChatAvatar src={appAvatar} type={'AI'} />
|
||||||
<Flex {...controlContainerStyle} ml={3}>
|
<Flex
|
||||||
|
{...controlContainerStyle}
|
||||||
|
ml={3}
|
||||||
|
display={index === chatHistory.length - 1 && isChatting ? 'none' : 'flex'}
|
||||||
|
>
|
||||||
<MyTooltip label={'复制'}>
|
<MyTooltip label={'复制'}>
|
||||||
<MyIcon
|
<MyIcon
|
||||||
{...controlIconStyle}
|
{...controlIconStyle}
|
||||||
@@ -984,69 +988,74 @@ const ChatBox = (
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* select one dataset to insert markData */}
|
{showMarkIcon && (
|
||||||
<SelectDataset
|
<>
|
||||||
isOpen={!!adminMarkData && !adminMarkData.kbId}
|
{/* select one dataset to insert markData */}
|
||||||
onClose={() => setAdminMarkData(undefined)}
|
<SelectDataset
|
||||||
// @ts-ignore
|
isOpen={!!adminMarkData && !adminMarkData.kbId}
|
||||||
onSuccess={(kbId) => setAdminMarkData((state) => ({ ...state, kbId }))}
|
onClose={() => setAdminMarkData(undefined)}
|
||||||
/>
|
// @ts-ignore
|
||||||
{/* edit markData modal */}
|
onSuccess={(kbId) => setAdminMarkData((state) => ({ ...state, kbId }))}
|
||||||
{adminMarkData && adminMarkData.kbId && (
|
/>
|
||||||
<InputDataModal
|
|
||||||
onClose={() => setAdminMarkData(undefined)}
|
|
||||||
onSuccess={async (data) => {
|
|
||||||
if (!adminMarkData.kbId || !data.dataId) {
|
|
||||||
return setAdminMarkData(undefined);
|
|
||||||
}
|
|
||||||
const adminFeedback = {
|
|
||||||
kbId: adminMarkData.kbId,
|
|
||||||
dataId: data.dataId,
|
|
||||||
content: data.a
|
|
||||||
};
|
|
||||||
|
|
||||||
// update dom
|
{/* edit markData modal */}
|
||||||
setChatHistory((state) =>
|
{adminMarkData && adminMarkData.kbId && (
|
||||||
state.map((chatItem) =>
|
<InputDataModal
|
||||||
chatItem.dataId === adminMarkData.chatItemId
|
onClose={() => setAdminMarkData(undefined)}
|
||||||
? {
|
onSuccess={async (data) => {
|
||||||
...chatItem,
|
if (!adminMarkData.kbId || !data.dataId) {
|
||||||
adminFeedback
|
return setAdminMarkData(undefined);
|
||||||
}
|
}
|
||||||
: chatItem
|
const adminFeedback = {
|
||||||
)
|
kbId: adminMarkData.kbId,
|
||||||
);
|
dataId: data.dataId,
|
||||||
// request to update adminFeedback
|
content: data.a
|
||||||
try {
|
};
|
||||||
adminUpdateChatFeedback({
|
|
||||||
chatItemId: adminMarkData.chatItemId,
|
|
||||||
...adminFeedback
|
|
||||||
});
|
|
||||||
|
|
||||||
if (readFeedbackData) {
|
// update dom
|
||||||
userUpdateChatFeedback({
|
|
||||||
chatItemId: readFeedbackData.chatItemId,
|
|
||||||
userFeedback: undefined
|
|
||||||
});
|
|
||||||
setChatHistory((state) =>
|
setChatHistory((state) =>
|
||||||
state.map((chatItem) =>
|
state.map((chatItem) =>
|
||||||
chatItem.dataId === readFeedbackData.chatItemId
|
chatItem.dataId === adminMarkData.chatItemId
|
||||||
? { ...chatItem, userFeedback: undefined }
|
? {
|
||||||
|
...chatItem,
|
||||||
|
adminFeedback
|
||||||
|
}
|
||||||
: chatItem
|
: chatItem
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
setReadFeedbackData(undefined);
|
// request to update adminFeedback
|
||||||
}
|
try {
|
||||||
} catch (error) {}
|
adminUpdateChatFeedback({
|
||||||
setAdminMarkData(undefined);
|
chatItemId: adminMarkData.chatItemId,
|
||||||
}}
|
...adminFeedback
|
||||||
kbId={adminMarkData.kbId}
|
});
|
||||||
defaultValues={{
|
|
||||||
dataId: adminMarkData.dataId,
|
if (readFeedbackData) {
|
||||||
q: adminMarkData.q,
|
userUpdateChatFeedback({
|
||||||
a: adminMarkData.a
|
chatItemId: readFeedbackData.chatItemId,
|
||||||
}}
|
userFeedback: undefined
|
||||||
/>
|
});
|
||||||
|
setChatHistory((state) =>
|
||||||
|
state.map((chatItem) =>
|
||||||
|
chatItem.dataId === readFeedbackData.chatItemId
|
||||||
|
? { ...chatItem, userFeedback: undefined }
|
||||||
|
: chatItem
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setReadFeedbackData(undefined);
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
setAdminMarkData(undefined);
|
||||||
|
}}
|
||||||
|
kbId={adminMarkData.kbId}
|
||||||
|
defaultValues={{
|
||||||
|
dataId: adminMarkData.dataId,
|
||||||
|
q: adminMarkData.q,
|
||||||
|
a: adminMarkData.a
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@@ -88,8 +88,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
bodyParser: {
|
responseLimit: '20mb'
|
||||||
sizeLimit: '20mb'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -101,8 +101,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
bodyParser: {
|
responseLimit: '10mb'
|
||||||
sizeLimit: '10mb'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -7,6 +7,7 @@ import { UpdateFileProps } from '@/api/core/dataset/file.d';
|
|||||||
import { Types } from 'mongoose';
|
import { Types } from 'mongoose';
|
||||||
import { PgClient } from '@/service/pg';
|
import { PgClient } from '@/service/pg';
|
||||||
import { PgDatasetTableName } from '@/constants/plugin';
|
import { PgDatasetTableName } from '@/constants/plugin';
|
||||||
|
import { addLog } from '@/service/utils/tools';
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
try {
|
try {
|
||||||
@@ -59,6 +60,7 @@ async function updateDatasetSource(data: { fileId: string; userId: string; name?
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
addLog.error(`Update dataset source error`, error);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
updateDatasetSource(data);
|
updateDatasetSource(data);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
@@ -173,8 +173,6 @@ export async function pushDataToKb({
|
|||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
bodyParser: {
|
responseLimit: '12mb'
|
||||||
sizeLimit: '12mb'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -477,8 +477,6 @@ export function getSystemVariable({ timezone }: { timezone: string }) {
|
|||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
bodyParser: {
|
responseLimit: '20mb'
|
||||||
sizeLimit: '20mb'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -5,7 +5,9 @@ import { authUser } from '@/service/utils/auth';
|
|||||||
import { PgDatasetTableName } from '@/constants/plugin';
|
import { PgDatasetTableName } from '@/constants/plugin';
|
||||||
import { findAllChildrenIds } from '../delete';
|
import { findAllChildrenIds } from '../delete';
|
||||||
import QueryStream from 'pg-query-stream';
|
import QueryStream from 'pg-query-stream';
|
||||||
import Papa from 'papaparse';
|
import { PgClient } from '@/service/pg';
|
||||||
|
import { addLog } from '@/service/utils/tools';
|
||||||
|
import { responseWriteController } from '@/service/common/stream';
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
try {
|
try {
|
||||||
@@ -24,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
|
|
||||||
const exportIds = [kbId, ...(await findAllChildrenIds(kbId))];
|
const exportIds = [kbId, ...(await findAllChildrenIds(kbId))];
|
||||||
|
|
||||||
const thirtyMinutesAgo = new Date(
|
const limitMinutesAgo = new Date(
|
||||||
Date.now() - (global.feConfigs?.limit?.exportLimitMinutes || 0) * 60 * 1000
|
Date.now() - (global.feConfigs?.limit?.exportLimitMinutes || 0) * 60 * 1000
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
_id: userId,
|
_id: userId,
|
||||||
$or: [
|
$or: [
|
||||||
{ 'limit.exportKbTime': { $exists: false } },
|
{ 'limit.exportKbTime': { $exists: false } },
|
||||||
{ 'limit.exportKbTime': { $lte: thirtyMinutesAgo } }
|
{ 'limit.exportKbTime': { $lte: limitMinutesAgo } }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
'_id limit'
|
'_id limit'
|
||||||
@@ -45,6 +47,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
throw new Error(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`);
|
throw new Error(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { rows } = await PgClient.query(
|
||||||
|
`SELECT count(id) FROM ${PgDatasetTableName} where user_id='${userId}' AND kb_id IN (${exportIds
|
||||||
|
.map((id) => `'${id}'`)
|
||||||
|
.join(',')})`
|
||||||
|
);
|
||||||
|
const total = rows?.[0]?.count || 0;
|
||||||
|
|
||||||
|
addLog.info(`export datasets: ${userId}`, { total });
|
||||||
|
|
||||||
|
if (total > 100000) {
|
||||||
|
throw new Error('数据量超出 10 万,无法导出');
|
||||||
|
}
|
||||||
|
|
||||||
// connect pg
|
// connect pg
|
||||||
global.pgClient.connect((err, client, done) => {
|
global.pgClient.connect((err, client, done) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -52,6 +67,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
res.end('Error connecting to database');
|
res.end('Error connecting to database');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log('export data');
|
||||||
|
|
||||||
// create pg select stream
|
// create pg select stream
|
||||||
const query = new QueryStream(
|
const query = new QueryStream(
|
||||||
`SELECT q, a, source FROM ${PgDatasetTableName} where user_id='${userId}' AND kb_id IN (${exportIds
|
`SELECT q, a, source FROM ${PgDatasetTableName} where user_id='${userId}' AND kb_id IN (${exportIds
|
||||||
@@ -65,11 +82,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
|
|
||||||
res.write('index,content,source');
|
res.write('index,content,source');
|
||||||
|
|
||||||
// parse data every row
|
const write = responseWriteController({
|
||||||
stream.on('data', (row: { q: string; a: string; source?: string }) => {
|
res,
|
||||||
const csv = Papa.unparse([row], { header: false });
|
readStream: stream
|
||||||
res.write(`\n${csv}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// parse data every row
|
||||||
|
stream.on('data', ({ q, a, source }: { q: string; a: string; source?: string }) => {
|
||||||
|
if (res.closed) {
|
||||||
|
return stream.destroy();
|
||||||
|
}
|
||||||
|
write(`\n"${q}","${a || ''}","${source || ''}"`);
|
||||||
|
});
|
||||||
|
// finish
|
||||||
stream.on('end', async () => {
|
stream.on('end', async () => {
|
||||||
try {
|
try {
|
||||||
// update export time
|
// update export time
|
||||||
@@ -98,8 +123,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
bodyParser: {
|
responseLimit: '100mb'
|
||||||
sizeLimit: '200mb'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -12,11 +12,13 @@ import MyIcon from '@/components/Icon';
|
|||||||
import CloseIcon from '@/components/Icon/close';
|
import CloseIcon from '@/components/Icon/close';
|
||||||
import DeleteIcon, { hoverDeleteStyles } from '@/components/Icon/delete';
|
import DeleteIcon, { hoverDeleteStyles } from '@/components/Icon/delete';
|
||||||
import MyTooltip from '@/components/MyTooltip';
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
import { QuestionOutlineIcon, InfoOutlineIcon } from '@chakra-ui/icons';
|
||||||
import { TrainingModeEnum } from '@/constants/plugin';
|
import { TrainingModeEnum } from '@/constants/plugin';
|
||||||
import FileSelect, { type FileItemType } from './FileSelect';
|
import FileSelect, { type FileItemType } from './FileSelect';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { updateDatasetFile } from '@/api/core/dataset/file';
|
import { updateDatasetFile } from '@/api/core/dataset/file';
|
||||||
|
import { Prompt_AgentQA } from '@/prompts/core/agent';
|
||||||
|
import { replaceVariable } from '@/utils/common/tools/text';
|
||||||
|
|
||||||
const fileExtension = '.txt, .doc, .docx, .pdf, .md';
|
const fileExtension = '.txt, .doc, .docx, .pdf, .md';
|
||||||
|
|
||||||
@@ -52,6 +54,12 @@ const QAImport = ({ kbId }: { kbId: string }) => {
|
|||||||
content: `该任务无法终止!导入后会自动调用大模型生成问答对,会有一些细节丢失,请确认!如果余额不足,未完成的任务会被暂停。`
|
content: `该任务无法终止!导入后会自动调用大模型生成问答对,会有一些细节丢失,请确认!如果余额不足,未完成的任务会被暂停。`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const previewQAPrompt = useMemo(() => {
|
||||||
|
return replaceVariable(Prompt_AgentQA.prompt, {
|
||||||
|
theme: prompt || Prompt_AgentQA.defaultTheme
|
||||||
|
});
|
||||||
|
}, [prompt]);
|
||||||
|
|
||||||
const { mutate: onclickUpload, isLoading: uploading } = useMutation({
|
const { mutate: onclickUpload, isLoading: uploading } = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const chunks = files.map((file) => file.chunks).flat();
|
const chunks = files.map((file) => file.chunks).flat();
|
||||||
@@ -74,7 +82,7 @@ const QAImport = ({ kbId }: { kbId: string }) => {
|
|||||||
kbId,
|
kbId,
|
||||||
data: chunks.slice(i, i + step),
|
data: chunks.slice(i, i + step),
|
||||||
mode: TrainingModeEnum.qa,
|
mode: TrainingModeEnum.qa,
|
||||||
prompt: prompt || '下面是一段长文本'
|
prompt: previewQAPrompt
|
||||||
});
|
});
|
||||||
|
|
||||||
success += insertLen;
|
success += insertLen;
|
||||||
@@ -202,21 +210,19 @@ const QAImport = ({ kbId }: { kbId: string }) => {
|
|||||||
<Box py={5}>
|
<Box py={5}>
|
||||||
<Box mb={2}>
|
<Box mb={2}>
|
||||||
QA 拆分引导词{' '}
|
QA 拆分引导词{' '}
|
||||||
<MyTooltip
|
<MyTooltip label={previewQAPrompt} forceShow>
|
||||||
label={`可输入关于文件内容的范围介绍,例如:\n1. Laf 的介绍\n2. xxx的简历\n最终会补全为: 关于{输入的内容}`}
|
<InfoOutlineIcon ml={1} />
|
||||||
forceShow
|
|
||||||
>
|
|
||||||
<QuestionOutlineIcon ml={1} />
|
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex alignItems={'center'} fontSize={'sm'}>
|
<Flex alignItems={'center'} fontSize={'sm'}>
|
||||||
<Box mr={2}>关于</Box>
|
<Box mr={2}>文件主题</Box>
|
||||||
<Input
|
<Input
|
||||||
|
fontSize={'sm'}
|
||||||
flex={1}
|
flex={1}
|
||||||
placeholder={'Laf 云函数的介绍'}
|
placeholder={Prompt_AgentQA.defaultTheme}
|
||||||
bg={'myWhite.500'}
|
bg={'myWhite.500'}
|
||||||
defaultValue={prompt}
|
defaultValue={prompt}
|
||||||
onBlur={(e) => (e.target.value ? setPrompt(`关于"${e.target.value}"`) : '')}
|
onChange={(e) => setPrompt(e.target.value || '')}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
|
16
client/src/prompts/core/agent.ts
Normal file
16
client/src/prompts/core/agent.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const Prompt_AgentQA = {
|
||||||
|
prompt: `我会给你一段文本,{{theme}},学习它们,并整理学习成果,要求为:
|
||||||
|
1. 提出最多 25 个问题。
|
||||||
|
2. 给出每个问题的答案。
|
||||||
|
3. 答案要详细完整,答案可以包含普通文字、链接、代码、表格、公示、媒体链接等 markdown 元素。
|
||||||
|
4. 按格式返回多个问题和答案:
|
||||||
|
|
||||||
|
Q1: 问题。
|
||||||
|
A1: 答案。
|
||||||
|
Q2:
|
||||||
|
A2:
|
||||||
|
……
|
||||||
|
|
||||||
|
我的文本:"""{{text}}"""`,
|
||||||
|
defaultTheme: '它们可能包含多个主题内容'
|
||||||
|
};
|
39
client/src/service/common/stream.ts
Normal file
39
client/src/service/common/stream.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { NextApiResponse } from 'next';
|
||||||
|
|
||||||
|
export function responseWriteController({
|
||||||
|
res,
|
||||||
|
readStream
|
||||||
|
}: {
|
||||||
|
res: NextApiResponse;
|
||||||
|
readStream: any;
|
||||||
|
}) {
|
||||||
|
res.on('drain', () => {
|
||||||
|
readStream.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (text: string) => {
|
||||||
|
const writeResult = res.write(text);
|
||||||
|
if (!writeResult) {
|
||||||
|
readStream.pause();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function responseWrite({
|
||||||
|
res,
|
||||||
|
write,
|
||||||
|
event,
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
res?: NextApiResponse;
|
||||||
|
write?: (text: string) => void;
|
||||||
|
event?: string;
|
||||||
|
data: string;
|
||||||
|
}) {
|
||||||
|
const Write = write || res?.write;
|
||||||
|
|
||||||
|
if (!Write) return;
|
||||||
|
|
||||||
|
event && Write(`event: ${event}\n`);
|
||||||
|
Write(`data: ${data}\n\n`);
|
||||||
|
}
|
@@ -11,6 +11,8 @@ import { gptMessage2ChatType } from '@/utils/adapt';
|
|||||||
import { addLog } from '../utils/tools';
|
import { addLog } from '../utils/tools';
|
||||||
import { splitText2Chunks } from '@/utils/file';
|
import { splitText2Chunks } from '@/utils/file';
|
||||||
import { countMessagesTokens } from '@/utils/common/tiktoken';
|
import { countMessagesTokens } from '@/utils/common/tiktoken';
|
||||||
|
import { replaceVariable } from '@/utils/common/tools/text';
|
||||||
|
import { Prompt_AgentQA } from '@/prompts/core/agent';
|
||||||
|
|
||||||
const reduceQueue = () => {
|
const reduceQueue = () => {
|
||||||
global.qaQueueLen = global.qaQueueLen > 0 ? global.qaQueueLen - 1 : 0;
|
global.qaQueueLen = global.qaQueueLen > 0 ? global.qaQueueLen - 1 : 0;
|
||||||
@@ -62,25 +64,18 @@ export async function generateQA(): Promise<any> {
|
|||||||
// 请求 chatgpt 获取回答
|
// 请求 chatgpt 获取回答
|
||||||
const response = await Promise.all(
|
const response = await Promise.all(
|
||||||
[data.q].map((text) => {
|
[data.q].map((text) => {
|
||||||
const modelTokenLimit = global.qaModel.maxToken || 16000;
|
|
||||||
const messages: ChatCompletionRequestMessage[] = [
|
const messages: ChatCompletionRequestMessage[] = [
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: `我会给你发送一段长文本,${
|
|
||||||
data.prompt ? `是${data.prompt},` : ''
|
|
||||||
}请学习它,并用 markdown 格式给出 25 个问题和答案,问题可以多样化、自由扩展;答案要详细、解读到位,答案包含普通文本、链接、代码、表格、公示、媒体链接等。按下面 QA 问答格式返回:
|
|
||||||
Q1:
|
|
||||||
A1:
|
|
||||||
Q2:
|
|
||||||
A2:
|
|
||||||
……`
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: text
|
content: data.prompt
|
||||||
|
? replaceVariable(data.prompt, { text })
|
||||||
|
: replaceVariable(Prompt_AgentQA.prompt, {
|
||||||
|
theme: Prompt_AgentQA.defaultTheme,
|
||||||
|
text
|
||||||
|
})
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const modelTokenLimit = global.qaModel.maxToken || 16000;
|
||||||
const promptsToken = countMessagesTokens({
|
const promptsToken = countMessagesTokens({
|
||||||
messages: gptMessage2ChatType(messages)
|
messages: gptMessage2ChatType(messages)
|
||||||
});
|
});
|
||||||
@@ -90,7 +85,7 @@ A2:
|
|||||||
.createChatCompletion(
|
.createChatCompletion(
|
||||||
{
|
{
|
||||||
model: global.qaModel.model,
|
model: global.qaModel.model,
|
||||||
temperature: 0.8,
|
temperature: 0.01,
|
||||||
messages,
|
messages,
|
||||||
stream: false,
|
stream: false,
|
||||||
max_tokens: maxToken
|
max_tokens: maxToken
|
||||||
@@ -197,6 +192,7 @@ A2:
|
|||||||
* 检查文本是否按格式返回
|
* 检查文本是否按格式返回
|
||||||
*/
|
*/
|
||||||
function formatSplitText(text: string) {
|
function formatSplitText(text: string) {
|
||||||
|
text = text.replace(/\\n/g, '\n'); // 将换行符替换为空格
|
||||||
const regex = /Q\d+:(\s*)(.*)(\s*)A\d+:(\s*)([\s\S]*?)(?=Q|$)/g; // 匹配Q和A的正则表达式
|
const regex = /Q\d+:(\s*)(.*)(\s*)A\d+:(\s*)([\s\S]*?)(?=Q|$)/g; // 匹配Q和A的正则表达式
|
||||||
const matches = text.matchAll(regex); // 获取所有匹配到的结果
|
const matches = text.matchAll(regex); // 获取所有匹配到的结果
|
||||||
|
|
||||||
@@ -207,8 +203,8 @@ function formatSplitText(text: string) {
|
|||||||
if (q && a) {
|
if (q && a) {
|
||||||
// 如果Q和A都存在,就将其添加到结果中
|
// 如果Q和A都存在,就将其添加到结果中
|
||||||
result.push({
|
result.push({
|
||||||
q,
|
q: `${q}\n${a.trim().replace(/\n\s*/g, '\n')}`,
|
||||||
a: a.trim().replace(/\n\s*/g, '\n')
|
a: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import type { NextApiResponse } from 'next';
|
import type { NextApiResponse } from 'next';
|
||||||
import { sseResponse } from '@/service/utils/tools';
|
|
||||||
import { ChatContextFilter } from '@/service/common/tiktoken';
|
import { ChatContextFilter } from '@/service/common/tiktoken';
|
||||||
import type { ChatItemType, QuoteItemType } from '@/types/chat';
|
import type { ChatItemType, QuoteItemType } from '@/types/chat';
|
||||||
import type { ChatHistoryItemResType } from '@/types/chat';
|
import type { ChatHistoryItemResType } from '@/types/chat';
|
||||||
@@ -21,6 +20,9 @@ import type { AIChatProps } from '@/types/core/aiChat';
|
|||||||
import { replaceVariable } from '@/utils/common/tools/text';
|
import { replaceVariable } from '@/utils/common/tools/text';
|
||||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||||
import { ModuleDispatchProps } from '@/types/core/modules';
|
import { ModuleDispatchProps } from '@/types/core/modules';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
import { responseWrite, responseWriteController } from '@/service/common/stream';
|
||||||
|
import { addLog } from '@/service/utils/tools';
|
||||||
|
|
||||||
export type ChatProps = ModuleDispatchProps<
|
export type ChatProps = ModuleDispatchProps<
|
||||||
AIChatProps & {
|
AIChatProps & {
|
||||||
@@ -324,7 +326,7 @@ function targetResponse({
|
|||||||
outputs.find((output) => output.key === TaskResponseKeyEnum.answerText)?.targets || [];
|
outputs.find((output) => output.key === TaskResponseKeyEnum.answerText)?.targets || [];
|
||||||
|
|
||||||
if (targets.length === 0) return;
|
if (targets.length === 0) return;
|
||||||
sseResponse({
|
responseWrite({
|
||||||
res,
|
res,
|
||||||
event: detail ? sseResponseEventEnum.answer : undefined,
|
event: detail ? sseResponseEventEnum.answer : undefined,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
@@ -342,42 +344,53 @@ async function streamResponse({
|
|||||||
detail: boolean;
|
detail: boolean;
|
||||||
response: any;
|
response: any;
|
||||||
}) {
|
}) {
|
||||||
let answer = '';
|
return new Promise<{ answer: string }>((resolve, reject) => {
|
||||||
let error: any = null;
|
const stream = response.data as Readable;
|
||||||
const parseData = new SSEParseData();
|
let answer = '';
|
||||||
|
const parseData = new SSEParseData();
|
||||||
|
|
||||||
try {
|
const write = responseWriteController({
|
||||||
for await (const chunk of response.data as any) {
|
res,
|
||||||
if (res.closed) break;
|
readStream: stream
|
||||||
const parse = parseStreamChunk(chunk);
|
});
|
||||||
|
|
||||||
|
stream.on('data', (data) => {
|
||||||
|
if (res.closed) {
|
||||||
|
stream.destroy();
|
||||||
|
return resolve({ answer });
|
||||||
|
}
|
||||||
|
|
||||||
|
const parse = parseStreamChunk(data);
|
||||||
parse.forEach((item) => {
|
parse.forEach((item) => {
|
||||||
const { data } = parseData.parse(item);
|
const { data } = parseData.parse(item);
|
||||||
if (!data || data === '[DONE]') return;
|
if (!data || data === '[DONE]') return;
|
||||||
|
|
||||||
const content: string = data?.choices?.[0]?.delta?.content || '';
|
const content: string = data?.choices?.[0]?.delta?.content || '';
|
||||||
error = data.error;
|
if (data.error) {
|
||||||
answer += content;
|
addLog.error(`SSE response`, data.error);
|
||||||
|
} else {
|
||||||
|
answer += content;
|
||||||
|
|
||||||
sseResponse({
|
responseWrite({
|
||||||
res,
|
write,
|
||||||
event: detail ? sseResponseEventEnum.answer : undefined,
|
event: detail ? sseResponseEventEnum.answer : undefined,
|
||||||
data: textAdaptGptResponse({
|
data: textAdaptGptResponse({
|
||||||
text: content
|
text: content
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
} catch (error) {
|
stream.on('end', () => {
|
||||||
console.log('pipe error', error);
|
resolve({ answer });
|
||||||
}
|
});
|
||||||
|
stream.on('close', () => {
|
||||||
if (error) {
|
resolve({ answer });
|
||||||
return Promise.reject(error);
|
});
|
||||||
}
|
stream.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
return {
|
});
|
||||||
answer
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHistoryPreview(completeMessages: ChatItemType[]) {
|
function getHistoryPreview(completeMessages: ChatItemType[]) {
|
||||||
|
Reference in New Issue
Block a user