mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 01:40:51 +00:00
4.8.9 test fix (#2291)
* perf: read file icon * perf:icon * fix: i18n * perf: hide pro api * perf: upload expired time * perf: upload file frequency limit * perf: upload file ux * perf: input file tip * perf: qa custom chunk size * feat: dataset openapi * fix: auth dataset list * fix: openapi doc * perf: zero temperature change to 0.01 * perf: read file prompt * perf: read file prompt * perf: free plan tip * feat: cron job usage
This commit is contained in:
@@ -20,6 +20,8 @@ import { defaultAppSelectFileConfig } from '@fastgpt/global/core/app/constants';
|
||||
import ChatFunctionTip from './Tip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { useMount } from 'ahooks';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
const FileSelect = ({
|
||||
forbidVision = false,
|
||||
@@ -31,7 +33,9 @@ const FileSelect = ({
|
||||
onChange: (e: AppFileSelectConfigType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const maxSelectFiles = Math.min(feConfigs?.uploadFileMaxAmount ?? 20, 30);
|
||||
|
||||
const formLabel = useMemo(
|
||||
() =>
|
||||
@@ -106,22 +110,27 @@ const FileSelect = ({
|
||||
)}
|
||||
</HStack>
|
||||
{!forbidVision && (
|
||||
<Box mt={2} color={'myGray.500'} fontSize={'xs'}>
|
||||
{t('app:image_upload_tip')}
|
||||
</Box>
|
||||
<Flex mt={2} color={'myGray.500'}>
|
||||
<Box fontSize={'xs'}>{t('app:image_upload_tip')}</Box>
|
||||
<ChatFunctionTip type="visionModel" />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Box mt={6}>
|
||||
<FormLabel>{t('app:upload_file_max_amount')}</FormLabel>
|
||||
<HStack spacing={1}>
|
||||
<FormLabel>{t('app:upload_file_max_amount')}</FormLabel>
|
||||
<QuestionTip label={t('app:upload_file_max_amount_tip')} />
|
||||
</HStack>
|
||||
|
||||
<Box mt={5}>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '1', value: 1 },
|
||||
{ label: '20', value: 20 }
|
||||
{ label: `${maxSelectFiles}`, value: maxSelectFiles }
|
||||
]}
|
||||
width={'100%'}
|
||||
min={1}
|
||||
max={20}
|
||||
max={maxSelectFiles}
|
||||
step={1}
|
||||
value={value.maxFiles ?? 5}
|
||||
onChange={(e) => {
|
||||
|
@@ -97,12 +97,8 @@ const TTSSelect = ({
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
<MyModal
|
||||
title={
|
||||
<>
|
||||
<MyIcon name={'core/app/simpleMode/tts'} mr={2} w={'20px'} />
|
||||
{t('common:core.app.TTS')}
|
||||
</>
|
||||
}
|
||||
iconSrc="core/app/simpleMode/tts"
|
||||
title={t('common:core.app.TTS')}
|
||||
isOpen={isOpen}
|
||||
onClose={onCloseTTSModal}
|
||||
w={'500px'}
|
||||
|
@@ -10,7 +10,8 @@ enum FnTypeEnum {
|
||||
tts = 'tts',
|
||||
variable = 'variable',
|
||||
welcome = 'welcome',
|
||||
file = 'file'
|
||||
file = 'file',
|
||||
visionModel = 'visionModel'
|
||||
}
|
||||
|
||||
const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
|
||||
@@ -52,7 +53,13 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
|
||||
icon: '/imgs/app/welcome-icon.svg',
|
||||
title: t('app:file_upload'),
|
||||
desc: t('app:file_upload_tip'),
|
||||
imgUrl: '/imgs/app/fileUploadPlaceholder.svg'
|
||||
imgUrl: '/imgs/app/fileUploadPlaceholder.png'
|
||||
},
|
||||
[FnTypeEnum.visionModel]: {
|
||||
icon: '/imgs/app/question.svg',
|
||||
title: t('app:vision_model_title'),
|
||||
desc: t('app:llm_use_vision_tip'),
|
||||
imgUrl: '/imgs/app/visionModel.png'
|
||||
}
|
||||
});
|
||||
const data = map.current[type];
|
||||
@@ -62,8 +69,8 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
|
||||
maxW={'420px'}
|
||||
ml={1}
|
||||
label={
|
||||
<Box>
|
||||
<Flex>
|
||||
<Box pt={2}>
|
||||
<Flex alignItems={'flex-start'}>
|
||||
<Image src={data.icon} w={'36px'} alt={''} />
|
||||
<Box ml={3}>
|
||||
<Box fontWeight="bold">{data.title}</Box>
|
||||
|
@@ -1,6 +1,15 @@
|
||||
import { useSpeech } from '@/web/common/hooks/useSpeech';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box, Flex, HStack, Image, Spinner, Textarea } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
CircularProgress,
|
||||
CircularProgressLabel,
|
||||
Flex,
|
||||
HStack,
|
||||
Image,
|
||||
Spinner,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
@@ -22,6 +31,8 @@ import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { clone } from 'lodash';
|
||||
import { formatFileSize } from '@fastgpt/global/common/file/tools';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
const InputGuideBox = dynamic(() => import('./InputGuideBox'));
|
||||
|
||||
@@ -103,6 +114,7 @@ const ChatInput = ({
|
||||
multiple: true,
|
||||
maxCount: maxSelectFiles
|
||||
});
|
||||
// Upload files
|
||||
useRequest2(
|
||||
async () => {
|
||||
const filterFiles = fileList.filter((item) => item.status === 0);
|
||||
@@ -110,30 +122,48 @@ const ChatInput = ({
|
||||
if (filterFiles.length === 0) return;
|
||||
|
||||
replaceFiles(fileList.map((item) => ({ ...item, status: 1 })));
|
||||
let errorFileIndex: number[] = [];
|
||||
|
||||
for (const file of filterFiles) {
|
||||
if (!file.rawFile) continue;
|
||||
await Promise.allSettled(
|
||||
filterFiles.map(async (file) => {
|
||||
const copyFile = clone(file);
|
||||
copyFile.status = 1;
|
||||
if (!copyFile.rawFile) return;
|
||||
|
||||
try {
|
||||
const { fileId, previewUrl } = await uploadFile2DB({
|
||||
file: file.rawFile,
|
||||
bucketName: 'chat',
|
||||
metadata: {
|
||||
chatId
|
||||
}
|
||||
});
|
||||
try {
|
||||
const fileIndex = fileList.findIndex((item) => item.id === file.id)!;
|
||||
|
||||
updateFiles(fileList.findIndex((item) => item.id === file.id)!, {
|
||||
...file,
|
||||
status: 1,
|
||||
url: `${location.origin}${previewUrl}`
|
||||
});
|
||||
} catch (error) {
|
||||
removeFiles(fileList.findIndex((item) => item.id === file.id)!);
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
// Start upload and update process
|
||||
const { previewUrl } = await uploadFile2DB({
|
||||
file: copyFile.rawFile,
|
||||
bucketName: 'chat',
|
||||
metadata: {
|
||||
chatId
|
||||
},
|
||||
percentListen(e) {
|
||||
copyFile.process = e;
|
||||
if (!copyFile.url) {
|
||||
updateFiles(fileIndex, copyFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update file url
|
||||
copyFile.url = `${location.origin}${previewUrl}`;
|
||||
updateFiles(fileIndex, copyFile);
|
||||
} catch (error) {
|
||||
errorFileIndex.push(fileList.findIndex((item) => item.id === file.id)!);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t(
|
||||
getErrText(error, t('common:error.upload_file_error_filename', { name: file.name }))
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
removeFiles(errorFileIndex);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
@@ -211,20 +241,23 @@ const ChatInput = ({
|
||||
);
|
||||
replaceFiles(concatFileList);
|
||||
},
|
||||
[fileList, maxSelectFiles, replaceFiles, toast, t]
|
||||
[fileList, maxSelectFiles, replaceFiles, toast, t, maxSize]
|
||||
);
|
||||
|
||||
/* on send */
|
||||
const handleSend = async (val?: string) => {
|
||||
if (!canSendMessage) return;
|
||||
const textareaValue = val || TextareaDom.current?.value || '';
|
||||
const handleSend = useCallback(
|
||||
async (val?: string) => {
|
||||
if (!canSendMessage) return;
|
||||
const textareaValue = val || TextareaDom.current?.value || '';
|
||||
|
||||
onSendMessage({
|
||||
text: textareaValue.trim(),
|
||||
files: fileList
|
||||
});
|
||||
replaceFiles([]);
|
||||
};
|
||||
onSendMessage({
|
||||
text: textareaValue.trim(),
|
||||
files: fileList
|
||||
});
|
||||
replaceFiles([]);
|
||||
},
|
||||
[TextareaDom, canSendMessage, fileList, onSendMessage, replaceFiles]
|
||||
);
|
||||
|
||||
/* whisper init */
|
||||
const { whisperModel } = useSystemStore();
|
||||
@@ -278,10 +311,360 @@ const ChatInput = ({
|
||||
startSpeak(finishWhisperTranscription);
|
||||
}, [finishWhisperTranscription, isSpeaking, startSpeak, stopSpeak]);
|
||||
|
||||
const RenderTranslateLoading = useMemo(
|
||||
() => (
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={10}
|
||||
pl={5}
|
||||
alignItems={'center'}
|
||||
bg={'white'}
|
||||
color={'primary.500'}
|
||||
visibility={isSpeaking && isTransCription ? 'visible' : 'hidden'}
|
||||
>
|
||||
<Spinner size={'sm'} mr={4} />
|
||||
{t('common:core.chat.Converting to text')}
|
||||
</Flex>
|
||||
),
|
||||
[isSpeaking, isTransCription, t]
|
||||
);
|
||||
const RenderFilePreview = useMemo(
|
||||
() =>
|
||||
fileList.length > 0 ? (
|
||||
<Flex
|
||||
maxH={'250px'}
|
||||
overflowY={'auto'}
|
||||
wrap={'wrap'}
|
||||
px={[2, 4]}
|
||||
pt={3}
|
||||
userSelect={'none'}
|
||||
gap={2}
|
||||
mb={fileList.length > 0 ? 2 : 0}
|
||||
>
|
||||
{fileList.map((item, index) => (
|
||||
<MyBox
|
||||
key={index}
|
||||
border={'sm'}
|
||||
boxShadow={
|
||||
'0px 2.571px 6.429px 0px rgba(19, 51, 107, 0.08), 0px 0px 0.643px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
rounded={'md'}
|
||||
position={'relative'}
|
||||
_hover={{
|
||||
'.close-icon': { display: 'block' }
|
||||
}}
|
||||
>
|
||||
<MyIcon
|
||||
name={'closeSolid'}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
color={'myGray.700'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.500' }}
|
||||
position={'absolute'}
|
||||
bg={'white'}
|
||||
right={'-8px'}
|
||||
top={'-8px'}
|
||||
onClick={() => removeFiles(index)}
|
||||
className="close-icon"
|
||||
display={['', 'none']}
|
||||
zIndex={10}
|
||||
/>
|
||||
{item.type === ChatFileTypeEnum.image && (
|
||||
<Image
|
||||
alt={'img'}
|
||||
src={item.icon}
|
||||
w={['2rem', '3rem']}
|
||||
h={['2rem', '3rem']}
|
||||
borderRadius={'md'}
|
||||
objectFit={'contain'}
|
||||
/>
|
||||
)}
|
||||
{item.type === ChatFileTypeEnum.file && (
|
||||
<HStack minW={['100px', '150px']} maxW={'250px'} p={2}>
|
||||
<MyIcon name={item.icon as any} w={['1.5rem', '2rem']} h={['1.5rem', '2rem']} />
|
||||
<Box flex={'1 0 0'} className="textEllipsis" fontSize={'xs'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</HStack>
|
||||
)}
|
||||
{/* Process */}
|
||||
{!item.url && (
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
inset="0"
|
||||
bg="rgba(255,255,255,0.4)"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<CircularProgress
|
||||
value={item.process}
|
||||
color="primary.600"
|
||||
bg={'white'}
|
||||
size={isPc ? '30px' : '35px'}
|
||||
>
|
||||
{/* <CircularProgressLabel>{item.process ?? 0}%</CircularProgressLabel> */}
|
||||
</CircularProgress>
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
))}
|
||||
</Flex>
|
||||
) : null,
|
||||
[fileList, isPc, removeFiles]
|
||||
);
|
||||
const RenderTextarea = useMemo(
|
||||
() => (
|
||||
<Flex alignItems={'flex-end'} mt={fileList.length > 0 ? 1 : 0} pl={[2, 4]}>
|
||||
{/* file selector */}
|
||||
{(showSelectFile || showSelectImg) && (
|
||||
<Flex
|
||||
h={'22px'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
cursor={'pointer'}
|
||||
transform={'translateY(1px)'}
|
||||
onClick={() => {
|
||||
if (isSpeaking) return;
|
||||
onOpenSelectFile();
|
||||
}}
|
||||
>
|
||||
<MyTooltip label={selectFileTip}>
|
||||
<MyIcon name={selectFileIcon as any} w={'18px'} color={'myGray.600'} />
|
||||
</MyTooltip>
|
||||
<File onSelect={onSelectFile} />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{/* input area */}
|
||||
<Textarea
|
||||
ref={TextareaDom}
|
||||
py={0}
|
||||
pl={2}
|
||||
pr={['30px', '48px']}
|
||||
border={'none'}
|
||||
_focusVisible={{
|
||||
border: 'none'
|
||||
}}
|
||||
placeholder={
|
||||
isSpeaking ? t('common:core.chat.Speaking') : t('common:core.chat.Type a message')
|
||||
}
|
||||
resize={'none'}
|
||||
rows={1}
|
||||
height={'22px'}
|
||||
lineHeight={'22px'}
|
||||
maxHeight={'50vh'}
|
||||
maxLength={-1}
|
||||
overflowY={'auto'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
boxShadow={'none !important'}
|
||||
color={'myGray.900'}
|
||||
isDisabled={isSpeaking}
|
||||
value={inputValue}
|
||||
fontSize={['md', 'sm']}
|
||||
onChange={(e) => {
|
||||
const textarea = e.target;
|
||||
textarea.style.height = textareaMinH;
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
setValue('input', textarea.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// enter send.(pc or iframe && enter and unPress shift)
|
||||
const isEnter = e.keyCode === 13;
|
||||
if (isEnter && TextareaDom.current && (e.ctrlKey || e.altKey)) {
|
||||
// Add a new line
|
||||
const index = TextareaDom.current.selectionStart;
|
||||
const val = TextareaDom.current.value;
|
||||
TextareaDom.current.value = `${val.slice(0, index)}\n${val.slice(index)}`;
|
||||
TextareaDom.current.selectionStart = index + 1;
|
||||
TextareaDom.current.selectionEnd = index + 1;
|
||||
|
||||
TextareaDom.current.style.height = textareaMinH;
|
||||
TextareaDom.current.style.height = `${TextareaDom.current.scrollHeight}px`;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 全选内容
|
||||
// @ts-ignore
|
||||
e.key === 'a' && e.ctrlKey && e.target?.select();
|
||||
|
||||
if ((isPc || window !== parent) && e.keyCode === 13 && !e.shiftKey) {
|
||||
handleSend();
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onPaste={(e) => {
|
||||
const clipboardData = e.clipboardData;
|
||||
if (clipboardData && (showSelectFile || showSelectImg)) {
|
||||
const items = clipboardData.items;
|
||||
const files = Array.from(items)
|
||||
.map((item) => (item.kind === 'file' ? item.getAsFile() : undefined))
|
||||
.filter((file) => {
|
||||
console.log(file);
|
||||
return file && fileTypeFilter(file);
|
||||
}) as File[];
|
||||
onSelectFile(files);
|
||||
|
||||
if (files.length > 0) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Flex alignItems={'center'} position={'absolute'} right={[2, 4]} bottom={['10px', '12px']}>
|
||||
{/* voice-input */}
|
||||
{whisperConfig.open && !havInput && !isChatting && !!whisperModel && (
|
||||
<>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
style={{
|
||||
height: '30px',
|
||||
width: isSpeaking && !isTransCription ? '100px' : 0,
|
||||
background: 'white',
|
||||
zIndex: 0
|
||||
}}
|
||||
/>
|
||||
{isSpeaking && (
|
||||
<MyTooltip label={t('common:core.chat.Cancel Speak')}>
|
||||
<Flex
|
||||
mr={2}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['26px', '32px']}
|
||||
w={['26px', '32px']}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#F5F5F8' }}
|
||||
onClick={() => stopSpeak(true)}
|
||||
>
|
||||
<MyIcon
|
||||
name={'core/chat/cancelSpeak'}
|
||||
width={['20px', '22px']}
|
||||
height={['20px', '22px']}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip
|
||||
label={
|
||||
isSpeaking ? t('common:core.chat.Finish Speak') : t('common:core.chat.Record')
|
||||
}
|
||||
>
|
||||
<Flex
|
||||
mr={2}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['26px', '32px']}
|
||||
w={['26px', '32px']}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#F5F5F8' }}
|
||||
onClick={onWhisperRecord}
|
||||
>
|
||||
<MyIcon
|
||||
name={isSpeaking ? 'core/chat/finishSpeak' : 'core/chat/recordFill'}
|
||||
width={['20px', '22px']}
|
||||
height={['20px', '22px']}
|
||||
color={isSpeaking ? 'primary.500' : 'myGray.600'}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
{/* send and stop icon */}
|
||||
{isSpeaking ? (
|
||||
<Box color={'#5A646E'} w={'36px'} textAlign={'right'} whiteSpace={'nowrap'}>
|
||||
{speakingTimeString}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['28px', '32px']}
|
||||
w={['28px', '32px']}
|
||||
borderRadius={'md'}
|
||||
bg={
|
||||
isSpeaking || isChatting
|
||||
? ''
|
||||
: !havInput || hasFileUploading
|
||||
? '#E5E5E5'
|
||||
: 'primary.500'
|
||||
}
|
||||
cursor={havInput ? 'pointer' : 'not-allowed'}
|
||||
lineHeight={1}
|
||||
onClick={() => {
|
||||
if (isChatting) {
|
||||
return onStop();
|
||||
}
|
||||
return handleSend();
|
||||
}}
|
||||
>
|
||||
{isChatting ? (
|
||||
<MyIcon
|
||||
animation={'zoomStopIcon 0.4s infinite alternate'}
|
||||
width={['22px', '25px']}
|
||||
height={['22px', '25px']}
|
||||
cursor={'pointer'}
|
||||
name={'stop'}
|
||||
color={'gray.500'}
|
||||
/>
|
||||
) : (
|
||||
<MyTooltip label={t('common:core.chat.Send Message')}>
|
||||
<MyIcon
|
||||
name={'core/chat/sendFill'}
|
||||
width={['18px', '20px']}
|
||||
height={['18px', '20px']}
|
||||
color={'white'}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
[
|
||||
File,
|
||||
TextareaDom,
|
||||
fileList.length,
|
||||
handleSend,
|
||||
hasFileUploading,
|
||||
havInput,
|
||||
inputValue,
|
||||
isChatting,
|
||||
isPc,
|
||||
isSpeaking,
|
||||
isTransCription,
|
||||
onOpenSelectFile,
|
||||
onSelectFile,
|
||||
onStop,
|
||||
onWhisperRecord,
|
||||
selectFileIcon,
|
||||
selectFileTip,
|
||||
setValue,
|
||||
showSelectFile,
|
||||
showSelectImg,
|
||||
speakingTimeString,
|
||||
stopSpeak,
|
||||
t,
|
||||
whisperConfig.open,
|
||||
whisperModel
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(800px, 100%)']} px={[0, 5]}>
|
||||
<Box
|
||||
pt={fileList.length > 0 ? '10px' : ['14px', '18px']}
|
||||
pt={fileList.length > 0 ? '0' : ['14px', '18px']}
|
||||
pb={['14px', '18px']}
|
||||
position={'relative'}
|
||||
boxShadow={isSpeaking ? `0 0 10px rgba(54,111,255,0.4)` : `0 0 10px rgba(0,0,0,0.2)`}
|
||||
@@ -313,317 +696,12 @@ const ChatInput = ({
|
||||
)}
|
||||
|
||||
{/* translate loading */}
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
right={0}
|
||||
zIndex={10}
|
||||
pl={5}
|
||||
alignItems={'center'}
|
||||
bg={'white'}
|
||||
color={'primary.500'}
|
||||
visibility={isSpeaking && isTransCription ? 'visible' : 'hidden'}
|
||||
>
|
||||
<Spinner size={'sm'} mr={4} />
|
||||
{t('common:core.chat.Converting to text')}
|
||||
</Flex>
|
||||
{RenderTranslateLoading}
|
||||
|
||||
{/* file preview */}
|
||||
<Flex
|
||||
wrap={'wrap'}
|
||||
px={[2, 4]}
|
||||
userSelect={'none'}
|
||||
gap={2}
|
||||
mb={fileList.length > 0 ? 2 : 0}
|
||||
>
|
||||
{fileList.map((item, index) => (
|
||||
<Box
|
||||
key={item.id}
|
||||
border={'1px solid #E8EBF0'}
|
||||
boxShadow={
|
||||
'0px 2.571px 6.429px 0px rgba(19, 51, 107, 0.08), 0px 0px 0.643px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
rounded={'md'}
|
||||
position={'relative'}
|
||||
_hover={{
|
||||
'.close-icon': { display: item.url ? 'block' : 'none' }
|
||||
}}
|
||||
>
|
||||
{/* uploading */}
|
||||
{!item.url && (
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
rounded={'md'}
|
||||
color={'primary.500'}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
right={0}
|
||||
bg={'rgba(255,255,255,0.8)'}
|
||||
>
|
||||
<Spinner />
|
||||
</Flex>
|
||||
)}
|
||||
<MyIcon
|
||||
name={'closeSolid'}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
color={'myGray.700'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.500' }}
|
||||
position={'absolute'}
|
||||
bg={'white'}
|
||||
right={'-8px'}
|
||||
top={'-8px'}
|
||||
onClick={() => {
|
||||
removeFiles(index);
|
||||
}}
|
||||
className="close-icon"
|
||||
display={['', 'none']}
|
||||
/>
|
||||
{item.type === ChatFileTypeEnum.image && (
|
||||
<Image
|
||||
alt={'img'}
|
||||
src={item.icon}
|
||||
w={['2rem', '3rem']}
|
||||
h={['2rem', '3rem']}
|
||||
borderRadius={'md'}
|
||||
objectFit={'contain'}
|
||||
/>
|
||||
)}
|
||||
{item.type === ChatFileTypeEnum.file && (
|
||||
<HStack minW={['100px', '150px']} maxW={'250px'} p={2}>
|
||||
<MyIcon name={item.icon as any} w={['1.5rem', '2rem']} h={['1.5rem', '2rem']} />
|
||||
<Box flex={'1 0 0'} className="textEllipsis" fontSize={'xs'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</HStack>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
{RenderFilePreview}
|
||||
|
||||
<Flex alignItems={'flex-end'} mt={fileList.length > 0 ? 1 : 0} pl={[2, 4]}>
|
||||
{/* file selector */}
|
||||
{(showSelectFile || showSelectImg) && (
|
||||
<Flex
|
||||
h={'22px'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
cursor={'pointer'}
|
||||
transform={'translateY(1px)'}
|
||||
onClick={() => {
|
||||
if (isSpeaking) return;
|
||||
onOpenSelectFile();
|
||||
}}
|
||||
>
|
||||
<MyTooltip label={selectFileTip}>
|
||||
<MyIcon name={selectFileIcon as any} w={'18px'} color={'myGray.600'} />
|
||||
</MyTooltip>
|
||||
<File onSelect={onSelectFile} />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{/* input area */}
|
||||
<Textarea
|
||||
ref={TextareaDom}
|
||||
py={0}
|
||||
pl={2}
|
||||
pr={['30px', '48px']}
|
||||
border={'none'}
|
||||
_focusVisible={{
|
||||
border: 'none'
|
||||
}}
|
||||
placeholder={
|
||||
isSpeaking ? t('common:core.chat.Speaking') : t('common:core.chat.Type a message')
|
||||
}
|
||||
resize={'none'}
|
||||
rows={1}
|
||||
height={'22px'}
|
||||
lineHeight={'22px'}
|
||||
maxHeight={'50vh'}
|
||||
maxLength={-1}
|
||||
overflowY={'auto'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
wordBreak={'break-all'}
|
||||
boxShadow={'none !important'}
|
||||
color={'myGray.900'}
|
||||
isDisabled={isSpeaking}
|
||||
value={inputValue}
|
||||
fontSize={['md', 'sm']}
|
||||
onChange={(e) => {
|
||||
const textarea = e.target;
|
||||
textarea.style.height = textareaMinH;
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
setValue('input', textarea.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
// enter send.(pc or iframe && enter and unPress shift)
|
||||
const isEnter = e.keyCode === 13;
|
||||
if (isEnter && TextareaDom.current && (e.ctrlKey || e.altKey)) {
|
||||
// Add a new line
|
||||
const index = TextareaDom.current.selectionStart;
|
||||
const val = TextareaDom.current.value;
|
||||
TextareaDom.current.value = `${val.slice(0, index)}\n${val.slice(index)}`;
|
||||
TextareaDom.current.selectionStart = index + 1;
|
||||
TextareaDom.current.selectionEnd = index + 1;
|
||||
|
||||
TextareaDom.current.style.height = textareaMinH;
|
||||
TextareaDom.current.style.height = `${TextareaDom.current.scrollHeight}px`;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 全选内容
|
||||
// @ts-ignore
|
||||
e.key === 'a' && e.ctrlKey && e.target?.select();
|
||||
|
||||
if ((isPc || window !== parent) && e.keyCode === 13 && !e.shiftKey) {
|
||||
handleSend();
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onPaste={(e) => {
|
||||
const clipboardData = e.clipboardData;
|
||||
if (clipboardData && (showSelectFile || showSelectImg)) {
|
||||
const items = clipboardData.items;
|
||||
const files = Array.from(items)
|
||||
.map((item) => (item.kind === 'file' ? item.getAsFile() : undefined))
|
||||
.filter((file) => {
|
||||
console.log(file);
|
||||
return file && fileTypeFilter(file);
|
||||
}) as File[];
|
||||
onSelectFile(files);
|
||||
|
||||
if (files.length > 0) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
position={'absolute'}
|
||||
right={[2, 4]}
|
||||
bottom={['10px', '12px']}
|
||||
>
|
||||
{/* voice-input */}
|
||||
{whisperConfig.open && !havInput && !isChatting && !!whisperModel && (
|
||||
<>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
style={{
|
||||
height: '30px',
|
||||
width: isSpeaking && !isTransCription ? '100px' : 0,
|
||||
background: 'white',
|
||||
zIndex: 0
|
||||
}}
|
||||
/>
|
||||
{isSpeaking && (
|
||||
<MyTooltip label={t('common:core.chat.Cancel Speak')}>
|
||||
<Flex
|
||||
mr={2}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['26px', '32px']}
|
||||
w={['26px', '32px']}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#F5F5F8' }}
|
||||
onClick={() => stopSpeak(true)}
|
||||
>
|
||||
<MyIcon
|
||||
name={'core/chat/cancelSpeak'}
|
||||
width={['20px', '22px']}
|
||||
height={['20px', '22px']}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip
|
||||
label={
|
||||
isSpeaking ? t('common:core.chat.Finish Speak') : t('common:core.chat.Record')
|
||||
}
|
||||
>
|
||||
<Flex
|
||||
mr={2}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['26px', '32px']}
|
||||
w={['26px', '32px']}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: '#F5F5F8' }}
|
||||
onClick={onWhisperRecord}
|
||||
>
|
||||
<MyIcon
|
||||
name={isSpeaking ? 'core/chat/finishSpeak' : 'core/chat/recordFill'}
|
||||
width={['20px', '22px']}
|
||||
height={['20px', '22px']}
|
||||
color={isSpeaking ? 'primary.500' : 'myGray.600'}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
{/* send and stop icon */}
|
||||
{isSpeaking ? (
|
||||
<Box color={'#5A646E'} w={'36px'} textAlign={'right'} whiteSpace={'nowrap'}>
|
||||
{speakingTimeString}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
flexShrink={0}
|
||||
h={['28px', '32px']}
|
||||
w={['28px', '32px']}
|
||||
borderRadius={'md'}
|
||||
bg={
|
||||
isSpeaking || isChatting
|
||||
? ''
|
||||
: !havInput || hasFileUploading
|
||||
? '#E5E5E5'
|
||||
: 'primary.500'
|
||||
}
|
||||
cursor={havInput ? 'pointer' : 'not-allowed'}
|
||||
lineHeight={1}
|
||||
onClick={() => {
|
||||
if (isChatting) {
|
||||
return onStop();
|
||||
}
|
||||
return handleSend();
|
||||
}}
|
||||
>
|
||||
{isChatting ? (
|
||||
<MyIcon
|
||||
animation={'zoomStopIcon 0.4s infinite alternate'}
|
||||
width={['22px', '25px']}
|
||||
height={['22px', '25px']}
|
||||
cursor={'pointer'}
|
||||
name={'stop'}
|
||||
color={'gray.500'}
|
||||
/>
|
||||
) : (
|
||||
<MyTooltip label={t('common:core.chat.Send Message')}>
|
||||
<MyIcon
|
||||
name={'core/chat/sendFill'}
|
||||
width={['18px', '20px']}
|
||||
height={['18px', '20px']}
|
||||
color={'white'}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
{RenderTextarea}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
@@ -15,6 +15,7 @@ export type UserInputFileItemType = {
|
||||
icon: string; // img is base64
|
||||
status: 0 | 1; // 0: uploading, 1: success
|
||||
url?: string;
|
||||
process?: number;
|
||||
};
|
||||
|
||||
export type ChatBoxInputFormType = {
|
||||
|
@@ -430,10 +430,6 @@ const PlanUsage = () => {
|
||||
|
||||
{isFreeTeam ? (
|
||||
<>
|
||||
<Flex mt="2" color={'#485264'} fontSize="sm">
|
||||
<Box>{t('common:support.wallet.Plan reset time')}:</Box>
|
||||
<Box ml={2}>{formatTime2YMD(standardPlan?.expiredTime)}</Box>
|
||||
</Flex>
|
||||
<Box mt="2" color={'#485264'} fontSize="sm">
|
||||
{t('common:info.free_plan')}
|
||||
</Box>
|
||||
|
@@ -7,8 +7,21 @@ import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { createFileToken } from '@fastgpt/service/support/permission/controller';
|
||||
import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { authFrequencyLimit } from '@/service/common/frequencyLimit/api';
|
||||
import { addSeconds } from 'date-fns';
|
||||
|
||||
const authUploadLimit = (tmbId: string) => {
|
||||
if (!global.feConfigs.uploadFileMaxAmount) return;
|
||||
return authFrequencyLimit({
|
||||
eventId: `${tmbId}-uploadfile`,
|
||||
maxAmount: global.feConfigs.uploadFileMaxAmount * 2,
|
||||
expiredTime: addSeconds(new Date(), 30) // 30s
|
||||
});
|
||||
};
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const start = Date.now();
|
||||
/* Creates the multer uploader */
|
||||
const upload = getUploadModel({
|
||||
maxSize: (global.feConfigs?.uploadFileMaxSize || 500) * 1024 * 1024
|
||||
@@ -16,9 +29,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const filePaths: string[] = [];
|
||||
|
||||
try {
|
||||
const { teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
|
||||
await authUploadLimit(tmbId);
|
||||
|
||||
const { file, bucketName, metadata } = await upload.doUpload(req, res);
|
||||
|
||||
const { teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
addLog.info(`Upload file success ${file.originalname}, cost ${Date.now() - start}ms`);
|
||||
|
||||
if (!bucketName) {
|
||||
throw new Error('bucketName is empty');
|
||||
@@ -34,22 +51,15 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
metadata: metadata
|
||||
});
|
||||
|
||||
jsonRes<{
|
||||
fileId: string;
|
||||
previewUrl: string;
|
||||
}>(res, {
|
||||
data: {
|
||||
fileId,
|
||||
previewUrl: `${ReadFileBaseUrl}?filename=${file.originalname}&token=${await createFileToken(
|
||||
{
|
||||
bucketName,
|
||||
teamId,
|
||||
tmbId,
|
||||
fileId
|
||||
}
|
||||
)}`
|
||||
}
|
||||
});
|
||||
return {
|
||||
fileId,
|
||||
previewUrl: `${ReadFileBaseUrl}?filename=${file.originalname}&token=${await createFileToken({
|
||||
bucketName,
|
||||
teamId,
|
||||
tmbId,
|
||||
fileId
|
||||
})}`
|
||||
};
|
||||
} catch (error) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
|
@@ -36,6 +36,7 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
return await authDataset({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: ReadPermissionVal,
|
||||
datasetId: parentId
|
||||
});
|
||||
|
@@ -37,11 +37,12 @@ type DatasetImportContextType = {
|
||||
setSources: React.Dispatch<React.SetStateAction<ImportSourceItemType[]>>;
|
||||
} & TrainingFiledType;
|
||||
|
||||
type ChunkSizeFieldType = 'embeddingChunkSize';
|
||||
type ChunkSizeFieldType = 'embeddingChunkSize' | 'qaChunkSize';
|
||||
export type ImportFormType = {
|
||||
mode: TrainingModeEnum;
|
||||
way: ImportProcessWayEnum;
|
||||
embeddingChunkSize: number;
|
||||
qaChunkSize: number;
|
||||
customSplitChar: string;
|
||||
qaPrompt: string;
|
||||
webSelector: string;
|
||||
@@ -147,7 +148,6 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
||||
steps
|
||||
});
|
||||
|
||||
// -----
|
||||
const vectorModel = datasetDetail.vectorModel;
|
||||
const agentModel = datasetDetail.agentModel;
|
||||
|
||||
@@ -156,6 +156,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
||||
mode: TrainingModeEnum.chunk,
|
||||
way: ImportProcessWayEnum.auto,
|
||||
embeddingChunkSize: vectorModel?.defaultToken || 512,
|
||||
qaChunkSize: Math.min(agentModel.maxResponse * 1, agentModel.maxContext * 0.7),
|
||||
customSplitChar: '',
|
||||
qaPrompt: Prompt_AgentQA.description,
|
||||
webSelector: ''
|
||||
@@ -168,6 +169,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
||||
const mode = processParamsForm.watch('mode');
|
||||
const way = processParamsForm.watch('way');
|
||||
const embeddingChunkSize = processParamsForm.watch('embeddingChunkSize');
|
||||
const qaChunkSize = processParamsForm.watch('qaChunkSize');
|
||||
const customSplitChar = processParamsForm.watch('customSplitChar');
|
||||
|
||||
const modeStaticParams: Record<TrainingModeEnum, TrainingFiledType> = {
|
||||
@@ -180,7 +182,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
||||
showChunkInput: false,
|
||||
showPromptInput: false,
|
||||
charsPointsPrice: agentModel.charsPointsPrice,
|
||||
priceTip: t('core.dataset.import.Auto mode Estimated Price Tips', {
|
||||
priceTip: t('common:core.dataset.import.Auto mode Estimated Price Tips', {
|
||||
price: agentModel.charsPointsPrice
|
||||
}),
|
||||
uploadRate: 100
|
||||
@@ -195,22 +197,22 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
||||
showChunkInput: true,
|
||||
showPromptInput: false,
|
||||
charsPointsPrice: vectorModel.charsPointsPrice,
|
||||
priceTip: t('core.dataset.import.Embedding Estimated Price Tips', {
|
||||
priceTip: t('common:core.dataset.import.Embedding Estimated Price Tips', {
|
||||
price: vectorModel.charsPointsPrice
|
||||
}),
|
||||
uploadRate: 150
|
||||
},
|
||||
[TrainingModeEnum.qa]: {
|
||||
chunkSizeField: 'embeddingChunkSize' as ChunkSizeFieldType,
|
||||
chunkSizeField: 'qaChunkSize' as ChunkSizeFieldType,
|
||||
chunkOverlapRatio: 0,
|
||||
maxChunkSize: 4096,
|
||||
minChunkSize: 512,
|
||||
autoChunkSize: agentModel.maxContext * 0.55 || 6000,
|
||||
chunkSize: embeddingChunkSize || agentModel.maxContext * 0.55 || 6000,
|
||||
maxChunkSize: Math.min(agentModel.maxResponse * 4, agentModel.maxContext * 0.7),
|
||||
minChunkSize: 4000,
|
||||
autoChunkSize: Math.min(agentModel.maxResponse * 1, agentModel.maxContext * 0.7),
|
||||
chunkSize: qaChunkSize,
|
||||
showChunkInput: true,
|
||||
showPromptInput: true,
|
||||
charsPointsPrice: agentModel.charsPointsPrice,
|
||||
priceTip: t('core.dataset.import.QA Estimated Price Tips', {
|
||||
priceTip: t('common:core.dataset.import.QA Estimated Price Tips', {
|
||||
price: agentModel?.charsPointsPrice
|
||||
}),
|
||||
uploadRate: 30
|
||||
@@ -228,7 +230,6 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
||||
customSplitChar
|
||||
}
|
||||
};
|
||||
|
||||
const chunkSize = wayStaticPrams[way].chunkSize;
|
||||
|
||||
const contextValue = {
|
||||
|
@@ -42,7 +42,8 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
showChunkInput,
|
||||
showPromptInput,
|
||||
maxChunkSize,
|
||||
priceTip
|
||||
priceTip,
|
||||
chunkSize
|
||||
} = useContextSelector(DatasetImportContext, (v) => v);
|
||||
const { getValues, setValue, register, watch } = processParamsForm;
|
||||
const { toast } = useToast();
|
||||
@@ -74,8 +75,15 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
);
|
||||
|
||||
return (
|
||||
<Box h={'100%'} display={['block', 'flex']} gap={5} fontSize={'sm'}>
|
||||
<Box flex={'1 0 0'} minW={['auto', '540px']} maxW={'600px'}>
|
||||
<Box h={'100%'} display={['block', 'flex']} fontSize={'sm'}>
|
||||
<Box
|
||||
flex={'1 0 0'}
|
||||
minW={['auto', '540px']}
|
||||
maxW={'600px'}
|
||||
h={['auto', '100%']}
|
||||
overflow={'auto'}
|
||||
pr={[0, 3]}
|
||||
>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'common/settingLight'} w={'20px'} />
|
||||
<Box fontSize={'md'}>{t('common:core.dataset.import.Data process params')}</Box>
|
||||
@@ -138,7 +146,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
}}
|
||||
>
|
||||
<MyTooltip
|
||||
label={t('core.dataset.import.Chunk Range', {
|
||||
label={t('common:core.dataset.import.Chunk Range', {
|
||||
min: minChunkSize,
|
||||
max: maxChunkSize
|
||||
})}
|
||||
@@ -148,6 +156,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
step={100}
|
||||
min={minChunkSize}
|
||||
max={maxChunkSize}
|
||||
value={chunkSize}
|
||||
onChange={(e) => {
|
||||
setValue(chunkSizeField, +e);
|
||||
}}
|
||||
@@ -279,7 +288,7 @@ function DataProcess({ showPreviewChunks = true }: { showPreviewChunks: boolean
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} w={['auto', '0']}>
|
||||
<Box flex={'1 0 0'} w={['auto', '0']} h={['auto', '100%']} overflow={'auto'} pl={[0, 3]}>
|
||||
<Preview showPreviewChunks={showPreviewChunks} />
|
||||
</Box>
|
||||
|
||||
|
8
projects/app/src/service/common/frequencyLimit/api.ts
Normal file
8
projects/app/src/service/common/frequencyLimit/api.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { AuthFrequencyLimitProps } from '@fastgpt/global/common/frequenctLimit/type';
|
||||
import { POST } from '@fastgpt/service/common/api/plusRequest';
|
||||
|
||||
export const authFrequencyLimit = (data: AuthFrequencyLimitProps) => {
|
||||
if (!global.feConfigs.isPlus) return;
|
||||
|
||||
return POST('/common/freequencyLimit/auth', data);
|
||||
};
|
@@ -1,4 +1,5 @@
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { defaultApp } from '@/web/core/app/constants';
|
||||
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
initWorkflowEdgeStatus,
|
||||
storeNodes2RuntimeNodes
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||
@@ -29,7 +31,7 @@ export const getScheduleTriggerApp = async () => {
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(app.tmbId);
|
||||
|
||||
try {
|
||||
await dispatchWorkFlow({
|
||||
const { flowUsages } = await dispatchWorkFlow({
|
||||
chatId: getNanoid(),
|
||||
user,
|
||||
mode: 'chat',
|
||||
@@ -53,6 +55,14 @@ export const getScheduleTriggerApp = async () => {
|
||||
detail: false,
|
||||
maxRunTimes: 200
|
||||
});
|
||||
pushChatUsage({
|
||||
appName: app.name,
|
||||
appId: app._id,
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId),
|
||||
source: UsageSourceEnum.cronJob,
|
||||
flowUsages
|
||||
});
|
||||
} catch (error) {
|
||||
addLog.error('Schedule trigger error', error);
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import {
|
||||
import { defaultDatasetDetail } from '../constants';
|
||||
import { DatasetUpdateBody } from '@fastgpt/global/core/dataset/api';
|
||||
import { DatasetItemType, DatasetTagType } from '@fastgpt/global/core/dataset/type';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
type DatasetPageContextType = {
|
||||
datasetId: string;
|
||||
@@ -83,6 +84,7 @@ export const DatasetPageContextProvider = ({
|
||||
datasetId: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
// dataset detail
|
||||
const [datasetDetail, setDatasetDetail] = useState(defaultDatasetDetail);
|
||||
@@ -123,6 +125,8 @@ export const DatasetPageContextProvider = ({
|
||||
const [allDatasetTags, setAllDatasetTags] = useState<DatasetTagType[]>([]);
|
||||
|
||||
const loadAllDatasetTags = async ({ id }: { id: string }) => {
|
||||
if (!feConfigs?.isPlus) return;
|
||||
|
||||
const { list } = await getAllTags(id);
|
||||
setAllDatasetTags(list);
|
||||
};
|
||||
|
@@ -4,7 +4,6 @@ import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { i18nT } from '@fastgpt/web/i18n/utils';
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
export const useSendCode = () => {
|
||||
@@ -29,8 +28,8 @@ export const useSendCode = () => {
|
||||
}, 1000);
|
||||
},
|
||||
{
|
||||
successToast: i18nT('user:password.code_sended'),
|
||||
errorToast: i18nT('user:password.code_send_error'),
|
||||
successToast: t('user:password.code_sended'),
|
||||
errorToast: t('user:password.code_send_error'),
|
||||
refreshDeps: [codeCountDown, feConfigs?.googleClientVerKey]
|
||||
}
|
||||
);
|
||||
|
Reference in New Issue
Block a user