Fixed the duplicate data check problem, history filter and add tts stream (#477)

This commit is contained in:
Archer
2023-11-16 16:22:08 +08:00
committed by GitHub
parent 16103029f5
commit fbe1d8cfed
31 changed files with 359 additions and 187 deletions

View File

@@ -1,12 +1,13 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
import { text2Speech } from '@fastgpt/service/core/ai/audio/speech';
import { pushAudioSpeechBill } from '@/service/support/wallet/bill/push';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { authType2BillSource } from '@/service/support/wallet/bill/utils';
import { getAudioSpeechModel } from '@/service/core/ai/model';
import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
/*
1. get tts from chatItem store
@@ -18,50 +19,66 @@ import { authType2BillSource } from '@/service/support/wallet/bill/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { chatItemId, ttsConfig, input } = req.body as GetChatSpeechProps;
const { ttsConfig, input } = req.body as GetChatSpeechProps;
if (!ttsConfig.model || !ttsConfig.voice) {
throw new Error('model or voice not found');
}
const { teamId, tmbId, authType } = await authCert({ req, authToken: true });
const chatItem = await (async () => {
if (!chatItemId) return null;
return await MongoChatItem.findOne(
{
dataId: chatItemId
},
'tts'
);
})();
const ttsModel = getAudioSpeechModel(ttsConfig.model);
const voiceData = ttsModel.voices.find((item) => item.value === ttsConfig.voice);
if (chatItem?.tts) {
return jsonRes(res, {
data: chatItem.tts
});
if (!voiceData) {
throw new Error('voice not found');
}
const { tts, model } = await text2Speech({
const ttsBuffer = await MongoTTSBuffer.findOne(
{
bufferId: voiceData.bufferId,
text: input
},
'buffer'
);
if (ttsBuffer?.buffer) {
return res.end(new Uint8Array(ttsBuffer.buffer.buffer));
}
await text2Speech({
res,
input,
model: ttsConfig.model,
voice: ttsConfig.voice,
input
});
props: {
// temp code
baseUrl: ttsModel.baseUrl || '',
key: ttsModel.key || ''
},
onSuccess: async ({ model, buffer }) => {
try {
pushAudioSpeechBill({
model: model,
textLength: input.length,
tmbId,
teamId,
source: authType2BillSource({ authType })
});
(async () => {
if (!chatItem) return;
try {
chatItem.tts = tts;
await chatItem.save();
} catch (error) {}
})();
jsonRes(res, {
data: tts
});
pushAudioSpeechBill({
model: model,
textLength: input.length,
tmbId,
teamId,
source: authType2BillSource({ authType })
await MongoTTSBuffer.create({
bufferId: voiceData.bufferId,
text: input,
buffer
});
} catch (error) {}
},
onError: (err) => {
jsonRes(res, {
code: 500,
error: err
});
}
});
} catch (err) {
jsonRes(res, {

View File

@@ -34,8 +34,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
qaModels: global.qaModels,
cqModels: global.cqModels,
extractModels: global.extractModels,
qgModels: global.qgModels,
vectorModels: global.vectorModels,
audioSpeechModels: global.audioSpeechModels.map((item) => ({
...item,
baseUrl: undefined,
key: undefined
})),
priceMd: global.priceMd,
systemVersion: global.systemVersion || '0.0.0'
}

View File

@@ -86,6 +86,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (chatMessages[chatMessages.length - 1].obj !== ChatRoleEnum.Human) {
chatMessages.pop();
}
// user question
const question = chatMessages.pop();
if (!question) {
@@ -173,15 +174,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const isAppOwner = !shareId && String(user.team.tmbId) === String(app.tmbId);
/* format prompts */
const prompts = history.concat(gptMessage2ChatType(messages));
// set sse response headers
if (stream) {
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Cache-Control', 'no-cache, no-transform');
}
const concatHistory = history.concat(chatMessages);
/* start flow controller */
const { responseData, answerText } = await dispatchModules({
@@ -193,7 +186,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
tmbId: user.team.tmbId,
variables,
params: {
history: prompts,
history: concatHistory,
userChatInput: question.value
},
stream,

View File

@@ -6,10 +6,10 @@ import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'next-i18next';
import MySelect from '@/components/Select';
import { TTSTypeEnum } from '@/constants/app';
import { Text2SpeechVoiceEnum, openaiTTSModel } from '@fastgpt/global/core/ai/speech/constant';
import { AppTTSConfigType } from '@/types/app';
import { useAudioPlay } from '@/web/common/utils/voice';
import { useLoading } from '@/web/common/hooks/useLoading';
import { audioSpeechModels } from '@/web/common/system/staticData';
const TTSSelect = ({
value,
@@ -37,10 +37,16 @@ const TTSSelect = ({
if (e === TTSTypeEnum.none || e === TTSTypeEnum.web) {
onChange({ type: e as `${TTSTypeEnum}` });
} else {
const audioModel = audioSpeechModels.find((item) =>
item.voices.find((voice) => voice.value === e)
);
if (!audioModel) {
return;
}
onChange({
type: TTSTypeEnum.model,
model: openaiTTSModel,
voice: e as `${Text2SpeechVoiceEnum}`,
model: audioModel.model,
voice: e,
speed: 1
});
}
@@ -77,12 +83,7 @@ const TTSSelect = ({
list={[
{ label: t('core.app.tts.Close'), value: TTSTypeEnum.none },
{ label: t('core.app.tts.Web'), value: TTSTypeEnum.web },
{ label: 'Alloy', value: Text2SpeechVoiceEnum.alloy },
{ label: 'Echo', value: Text2SpeechVoiceEnum.echo },
{ label: 'Fable', value: Text2SpeechVoiceEnum.fable },
{ label: 'Onyx', value: Text2SpeechVoiceEnum.onyx },
{ label: 'Nova', value: Text2SpeechVoiceEnum.nova },
{ label: 'Shimmer', value: Text2SpeechVoiceEnum.shimmer }
...audioSpeechModels.map((item) => item.voices).flat()
]}
onchange={onclickChange}
/>

View File

@@ -2,11 +2,13 @@ import React from 'react';
import { Box, Flex, Button } from '@chakra-ui/react';
import { useConfirm } from '@/web/common/hooks/useConfirm';
import { useImportStore, SelectorContainer, PreviewFileOrChunk } from './Provider';
import { useTranslation } from 'next-i18next';
const fileExtension = '.csv';
const csvTemplate = `index,content\n"被索引的内容","对应的答案。CSV 中请注意内容不能包含双引号,双引号是列分割符号"\n"什么是 laf","laf 是一个云函数开发平台……",""\n"什么是 sealos","Sealos 是以 kubernetes 为内核的云操作系统发行版,可以……"`;
const CsvImport = () => {
const { t } = useTranslation();
const { successChunks, totalChunks, isUnselectedFile, onclickUpload, uploading } =
useImportStore();
@@ -24,6 +26,7 @@ const CsvImport = () => {
value: csvTemplate,
type: 'text/csv'
}}
tip={t('dataset.import csv tip')}
>
<Flex mt={3}>
<Button isDisabled={uploading} onClick={openConfirm(onclickUpload)}>

View File

@@ -55,6 +55,7 @@ export interface Props extends BoxProps {
};
showUrlFetch?: boolean;
showCreateFile?: boolean;
tip?: string;
}
const FileSelect = ({
@@ -65,6 +66,7 @@ const FileSelect = ({
fileTemplate,
showUrlFetch = true,
showCreateFile = true,
tip,
...props
}: Props) => {
const { datasetDetail } = useDatasetStore();
@@ -423,6 +425,7 @@ const FileSelect = ({
{t('file.Click to download file template', { name: fileTemplate.filename })}
</Box>
)}
{!!tip && <Box color={'myGray.500'}>{tip}</Box>}
{selectingText !== undefined && (
<FileSelectLoading loading text={selectingText} fixed={false} />
)}

View File

@@ -403,12 +403,14 @@ export const SelectorContainer = ({
showUrlFetch,
showCreateFile,
fileTemplate,
tip,
children
}: {
fileExtension: string;
showUrlFetch?: boolean;
showCreateFile?: boolean;
fileTemplate?: FileSelectProps['fileTemplate'];
tip?: string;
children: React.ReactNode;
}) => {
const { files, setPreviewFile, isUnselectedFile, setFiles, chunkLen } = useImportStore();
@@ -433,6 +435,7 @@ export const SelectorContainer = ({
showUrlFetch={showUrlFetch}
showCreateFile={showCreateFile}
fileTemplate={fileTemplate}
tip={tip}
py={isUnselectedFile ? '100px' : 5}
/>
{!isUnselectedFile && (

View File

@@ -117,10 +117,8 @@ const InputDataModal = ({
const { mutate: sureImportData, isLoading: isImporting } = useRequest({
mutationFn: async (e: InputDataType) => {
if (!e.q) {
return toast({
title: '匹配的知识点不能为空',
status: 'warning'
});
setCurrentTab(TabEnum.content);
return Promise.reject(t('dataset.data.input is empty'));
}
if (countPromptTokens(e.q) >= maxToken) {
return toast({