feat: markdown extension (#3663)
* feat: markdown extension * media cros * rerank test * default price * perf: default model * fix: cannot custom provider * fix: default model select * update bg * perf: default model selector * fix: usage export * i18n * fix: rerank * update init extension * perf: ip limit check * doubao model order * web default modle * perf: tts selector * perf: tts error * qrcode package
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
"next-i18next": "15.3.0",
|
||||
"nextjs-node-loader": "^1.1.5",
|
||||
"nprogress": "^0.2.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "18.3.1",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-dom": "18.3.1",
|
||||
@@ -81,6 +82,7 @@
|
||||
"@types/jsonwebtoken": "^9.0.3",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/react": "18.3.1",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
|
||||
1
projects/app/public/js/qrcode.min.js
vendored
1
projects/app/public/js/qrcode.min.js
vendored
File diff suppressed because one or more lines are too long
31
projects/app/src/components/Markdown/codeBlock/Audio.tsx
Normal file
31
projects/app/src/components/Markdown/codeBlock/Audio.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useMarkdownWidth } from '../hooks';
|
||||
|
||||
const AudioBlock = ({ code: audioUrl }: { code: string }) => {
|
||||
const { width, Ref } = useMarkdownWidth();
|
||||
|
||||
useEffect(() => {
|
||||
fetch(audioUrl?.trim(), {
|
||||
mode: 'cors',
|
||||
credentials: 'omit'
|
||||
})
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const audio = document.getElementById('player');
|
||||
audio?.setAttribute('src', url);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}, [audioUrl]);
|
||||
|
||||
return (
|
||||
<Box w={width} ref={Ref}>
|
||||
<audio id="player" controls style={{ width: '100%' }} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AudioBlock;
|
||||
31
projects/app/src/components/Markdown/codeBlock/Video.tsx
Normal file
31
projects/app/src/components/Markdown/codeBlock/Video.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useMarkdownWidth } from '../hooks';
|
||||
|
||||
const VideoBlock = ({ code: videoUrl }: { code: string }) => {
|
||||
const { width, Ref } = useMarkdownWidth();
|
||||
|
||||
useEffect(() => {
|
||||
fetch(videoUrl?.trim(), {
|
||||
mode: 'cors',
|
||||
credentials: 'omit'
|
||||
})
|
||||
.then((response) => response.blob())
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const video = document.getElementById('player');
|
||||
video?.setAttribute('src', url);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}, [videoUrl]);
|
||||
|
||||
return (
|
||||
<Box w={width} ref={Ref}>
|
||||
<video id="player" controls />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoBlock;
|
||||
@@ -16,7 +16,7 @@ import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMarkdownWidth } from '../hooks';
|
||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type.d';
|
||||
import { codeLight } from '../CodeLight';
|
||||
import { codeLight } from './CodeLight';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
const StyledButton = ({
|
||||
|
||||
@@ -13,12 +13,14 @@ import dynamic from 'next/dynamic';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { CodeClassNameEnum } from './utils';
|
||||
|
||||
const CodeLight = dynamic(() => import('./CodeLight'), { ssr: false });
|
||||
const CodeLight = dynamic(() => import('./codeBlock/CodeLight'), { ssr: false });
|
||||
const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false });
|
||||
const MdImage = dynamic(() => import('./img/Image'), { ssr: false });
|
||||
const EChartsCodeBlock = dynamic(() => import('./img/EChartsCodeBlock'), { ssr: false });
|
||||
const IframeCodeBlock = dynamic(() => import('./codeBlock/Iframe'), { ssr: false });
|
||||
const IframeHtmlCodeBlock = dynamic(() => import('./codeBlock/iframe-html'), { ssr: false });
|
||||
const VideoBlock = dynamic(() => import('./codeBlock/Video'), { ssr: false });
|
||||
const AudioBlock = dynamic(() => import('./codeBlock/Audio'), { ssr: false });
|
||||
|
||||
const ChatGuide = dynamic(() => import('./chat/Guide'), { ssr: false });
|
||||
const QuestionGuide = dynamic(() => import('./chat/QuestionGuide'), { ssr: false });
|
||||
@@ -139,6 +141,12 @@ function Code(e: any) {
|
||||
</IframeHtmlCodeBlock>
|
||||
);
|
||||
}
|
||||
if (codeType === CodeClassNameEnum.video) {
|
||||
return <VideoBlock code={strChildren} />;
|
||||
}
|
||||
if (codeType === CodeClassNameEnum.audio) {
|
||||
return <AudioBlock code={strChildren} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<CodeLight className={className} codeBlock={codeBlock} match={match}>
|
||||
|
||||
@@ -7,7 +7,9 @@ export enum CodeClassNameEnum {
|
||||
files = 'files',
|
||||
latex = 'latex',
|
||||
iframe = 'iframe',
|
||||
html = 'html'
|
||||
html = 'html',
|
||||
video = 'video',
|
||||
audio = 'audio'
|
||||
}
|
||||
|
||||
function htmlTableToLatex(html: string) {
|
||||
|
||||
@@ -80,7 +80,9 @@ const AIChatSettingsModal = ({
|
||||
const temperature = watch('temperature');
|
||||
const useVision = watch('aiChatVision');
|
||||
|
||||
const selectedModel = getWebLLMModel(model);
|
||||
const selectedModel = useMemo(() => {
|
||||
return getWebLLMModel(model);
|
||||
}, [model]);
|
||||
const llmSupportVision = !!selectedModel?.vision;
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
@@ -244,7 +246,7 @@ const AIChatSettingsModal = ({
|
||||
</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<InputSlider
|
||||
min={100}
|
||||
min={0}
|
||||
max={tokenLimit}
|
||||
step={200}
|
||||
isDisabled={maxToken === undefined}
|
||||
|
||||
@@ -7,8 +7,8 @@ import AISettingModal, { AIChatSettingsModalProps } from '@/components/core/ai/A
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useMount } from 'ahooks';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { getWebDefaultModel } from '@/web/common/system/utils';
|
||||
|
||||
type Props = {
|
||||
llmModelType?: `${LLMModelTypeEnum}`;
|
||||
@@ -24,7 +24,7 @@ const SettingLLMModel = ({
|
||||
...props
|
||||
}: AIChatSettingsModalProps & Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { llmModelList, defaultModels } = useSystemStore();
|
||||
const { llmModelList } = useSystemStore();
|
||||
|
||||
const model = defaultData.model;
|
||||
|
||||
@@ -39,16 +39,19 @@ const SettingLLMModel = ({
|
||||
}),
|
||||
[llmModelList, llmModelType]
|
||||
);
|
||||
const defaultModel = useMemo(() => {
|
||||
return getWebDefaultModel(modelList).model;
|
||||
}, [modelList]);
|
||||
|
||||
// Set default model
|
||||
useEffect(() => {
|
||||
if (!llmModelList.find((item) => item.model === model) && !!defaultModels.llm) {
|
||||
if (!modelList.find((item) => item.model === model) && !!defaultModel) {
|
||||
onChange({
|
||||
...defaultData,
|
||||
model: defaultModels.llm.model
|
||||
model: defaultModel
|
||||
});
|
||||
}
|
||||
}, [model, defaultData, llmModelList, defaultModels.llm, onChange]);
|
||||
}, [modelList, model, defaultModel, onChange]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenAIChatSetting,
|
||||
|
||||
@@ -65,7 +65,7 @@ const DatasetParamsModal = ({
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const { reRankModelList, llmModelList } = useSystemStore();
|
||||
const { reRankModelList, llmModelList, defaultModels } = useSystemStore();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [currentTabType, setCurrentTabType] = useState(SearchSettingTabEnum.searchMode);
|
||||
|
||||
@@ -82,7 +82,7 @@ const DatasetParamsModal = ({
|
||||
searchMode,
|
||||
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel || chatModelSelectList[0]?.value,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel || defaultModels.llm?.model,
|
||||
datasetSearchExtensionBg
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { Box, Button, Flex, ModalBody, useDisclosure, Image } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, ModalBody, useDisclosure, Image, HStack } from '@chakra-ui/react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TTSTypeEnum } from '@/web/core/app/constants';
|
||||
@@ -9,13 +9,15 @@ import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySlider from '@/components/Slider';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { defaultTTSConfig } from '@fastgpt/global/core/app/constants';
|
||||
import ChatFunctionTip from './Tip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '@/pageComponents/app/detail/context';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { getModelProvider } from '@fastgpt/global/core/ai/provider';
|
||||
import MultipleRowSelect from '@fastgpt/web/components/common/MySelect/MultipleRowSelect';
|
||||
|
||||
const TTSSelect = ({
|
||||
value = defaultTTSConfig,
|
||||
@@ -30,28 +32,57 @@ const TTSSelect = ({
|
||||
|
||||
const appId = useContextSelector(AppContext, (v) => v.appId);
|
||||
|
||||
const list = useMemo(
|
||||
const selectorList = useMemo(
|
||||
() => [
|
||||
{ label: t('common:core.app.tts.Close'), value: TTSTypeEnum.none },
|
||||
{ label: t('common:core.app.tts.Web'), value: TTSTypeEnum.web },
|
||||
...ttsModelList.map((item) => item?.voices || []).flat()
|
||||
{ label: t('app:tts_close'), value: TTSTypeEnum.none, children: [] },
|
||||
{ label: t('app:tts_browser'), value: TTSTypeEnum.web, children: [] },
|
||||
...ttsModelList.map((model) => {
|
||||
const providerData = getModelProvider(model.provider);
|
||||
return {
|
||||
label: (
|
||||
<HStack>
|
||||
<Avatar borderRadius={'0'} w={'1.25rem'} src={providerData.avatar} />
|
||||
<Box>{t(providerData.name)}</Box>
|
||||
</HStack>
|
||||
),
|
||||
value: model.model,
|
||||
children: model.voices.map((voice) => ({
|
||||
label: voice.label,
|
||||
value: voice.value
|
||||
}))
|
||||
};
|
||||
})
|
||||
],
|
||||
[ttsModelList, t]
|
||||
);
|
||||
|
||||
const formatValue = useMemo(() => {
|
||||
if (!value || !value.type) {
|
||||
return TTSTypeEnum.none;
|
||||
return [TTSTypeEnum.none, undefined];
|
||||
}
|
||||
if (value.type === TTSTypeEnum.none || value.type === TTSTypeEnum.web) {
|
||||
return value.type;
|
||||
return [value.type, undefined];
|
||||
}
|
||||
return value.voice;
|
||||
|
||||
return [value.model, value.voice];
|
||||
}, [value]);
|
||||
const formLabel = useMemo(
|
||||
() => list.find((item) => item.value === formatValue)?.label || t('common:common.UnKnow'),
|
||||
[formatValue, list, t]
|
||||
);
|
||||
const formLabel = useMemo(() => {
|
||||
const provider = selectorList.find((item) => item.value === formatValue[0]) || selectorList[0];
|
||||
const voice = provider.children.find((item) => item.value === formatValue[1]);
|
||||
return (
|
||||
<Box minW={'150px'} maxW={'220px'} className="textEllipsis">
|
||||
{voice ? (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box>{provider.label}</Box>
|
||||
<Box>-</Box>
|
||||
<Box>{voice.label}</Box>
|
||||
</Flex>
|
||||
) : (
|
||||
provider.label
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}, [formatValue, selectorList, t]);
|
||||
|
||||
const { playAudioByText, cancelAudio, audioLoading, audioPlaying } = useAudioPlay({
|
||||
appId,
|
||||
@@ -59,21 +90,16 @@ const TTSSelect = ({
|
||||
});
|
||||
|
||||
const onclickChange = useCallback(
|
||||
(e: string) => {
|
||||
if (e === TTSTypeEnum.none || e === TTSTypeEnum.web) {
|
||||
onChange({ type: e as `${TTSTypeEnum}` });
|
||||
(e: string[]) => {
|
||||
console.log(e, '-=');
|
||||
if (e[0] === TTSTypeEnum.none || e[0] === TTSTypeEnum.web) {
|
||||
onChange({ type: e[0] });
|
||||
} else {
|
||||
const audioModel = ttsModelList.find((item) =>
|
||||
item.voices?.find((voice) => voice.value === e)
|
||||
);
|
||||
if (!audioModel) {
|
||||
return;
|
||||
}
|
||||
onChange({
|
||||
...value,
|
||||
type: TTSTypeEnum.model,
|
||||
model: audioModel.model,
|
||||
voice: e
|
||||
model: e[0],
|
||||
voice: e[1]
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -113,7 +139,13 @@ const TTSSelect = ({
|
||||
<ModalBody px={[5, 16]} py={[4, 8]}>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<FormLabel>{t('common:core.app.tts.Speech model')}</FormLabel>
|
||||
<MySelect w={'220px'} value={formatValue} list={list} onchange={onclickChange} />
|
||||
<MultipleRowSelect
|
||||
rowMinWidth="160px"
|
||||
label={formLabel}
|
||||
value={formatValue}
|
||||
list={selectorList}
|
||||
onSelect={onclickChange}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={8} justifyContent={'space-between'}>
|
||||
<FormLabel>{t('common:core.app.tts.Speech speed')}</FormLabel>
|
||||
@@ -135,7 +167,7 @@ const TTSSelect = ({
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{formatValue !== TTSTypeEnum.none && (
|
||||
{formatValue[0] !== TTSTypeEnum.none && (
|
||||
<Flex mt={10} justifyContent={'end'}>
|
||||
{audioPlaying ? (
|
||||
<Flex>
|
||||
|
||||
@@ -25,7 +25,7 @@ const WelcomeTextConfig = (props: TextareaProps) => {
|
||||
mt={1.5}
|
||||
rows={6}
|
||||
fontSize={'sm'}
|
||||
bg={'white'}
|
||||
bg={'myGray.50'}
|
||||
minW={'384px'}
|
||||
placeholder={t('common:core.app.tip.welcomeTextTip')}
|
||||
autoHeight
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, ModalBody } from '@chakra-ui/react';
|
||||
import { checkBalancePayResult } from '@/web/support/wallet/bill/api';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import LightTip from '@fastgpt/web/components/common/LightTip';
|
||||
import Script from 'next/script';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
export type QRPayProps = {
|
||||
readPrice: number;
|
||||
@@ -27,21 +25,35 @@ const QRCodePayModal = ({
|
||||
}: QRPayProps & { tip?: string; onSuccess?: () => any }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const dom = useRef<HTMLDivElement>(null);
|
||||
const canvasRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const drawCode = useCallback(() => {
|
||||
if (dom.current && window.QRCode && !dom.current.innerHTML) {
|
||||
new window.QRCode(dom.current, {
|
||||
text: codeUrl,
|
||||
width: qrCodeSize,
|
||||
height: qrCodeSize,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: window.QRCode.CorrectLevel.H
|
||||
const canvas = document.createElement('canvas');
|
||||
QRCode.toCanvas(canvas, codeUrl, {
|
||||
width: qrCodeSize,
|
||||
margin: 0,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#ffffff'
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
if (canvasRef.current) {
|
||||
canvasRef.current.innerHTML = '';
|
||||
canvasRef.current.appendChild(canvas);
|
||||
} else {
|
||||
drawCode();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('QRCode generation error:', err);
|
||||
});
|
||||
}
|
||||
}, [codeUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
drawCode();
|
||||
}, [drawCode]);
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
const check = async () => {
|
||||
@@ -54,7 +66,7 @@ const QRCodePayModal = ({
|
||||
title: res,
|
||||
status: 'success'
|
||||
});
|
||||
return;
|
||||
return clearTimeout(timer);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: getErrText(error),
|
||||
@@ -63,9 +75,7 @@ const QRCodePayModal = ({
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
drawCode();
|
||||
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(check, 2000);
|
||||
};
|
||||
|
||||
@@ -75,23 +85,15 @@ const QRCodePayModal = ({
|
||||
}, [billId, drawCode, onSuccess, toast]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src={getWebReqUrl('/js/qrcode.min.js')}
|
||||
strategy="lazyOnload"
|
||||
onLoad={drawCode}
|
||||
></Script>
|
||||
|
||||
<MyModal isOpen title={t('common:user.Pay')} iconSrc="/imgs/modal/pay.svg">
|
||||
<ModalBody textAlign={'center'} pb={10} whiteSpace={'pre-wrap'}>
|
||||
{tip && <LightTip text={tip} mb={8} textAlign={'left'} />}
|
||||
<Box ref={dom} id={'payQRCode'} display={'inline-block'} h={`${qrCodeSize}px`}></Box>
|
||||
<Box mt={5} textAlign={'center'}>
|
||||
{t('common:pay.wechat', { price: readPrice })}
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
</>
|
||||
<MyModal isOpen title={t('common:user.Pay')} iconSrc="/imgs/modal/pay.svg">
|
||||
<ModalBody textAlign={'center'} pb={10} whiteSpace={'pre-wrap'}>
|
||||
{tip && <LightTip text={tip} mb={8} textAlign={'left'} />}
|
||||
<Box ref={canvasRef} display={'inline-block'} h={`${qrCodeSize}px`}></Box>
|
||||
<Box mt={5} textAlign={'center'}>
|
||||
{t('common:pay.wechat', { price: readPrice })}
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import {
|
||||
deleteSystemModel,
|
||||
getModelConfigJson,
|
||||
getSystemModelDefaultConfig,
|
||||
getSystemModelDetail,
|
||||
getSystemModelList,
|
||||
getTestModel,
|
||||
@@ -56,6 +57,7 @@ import { putUpdateWithJson } from '@/web/core/ai/config';
|
||||
import CopyBox from '@fastgpt/web/components/common/String/CopyBox';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { useRefresh } from '../../../../../../packages/web/hooks/useRefresh';
|
||||
|
||||
const MyModal = dynamic(() => import('@fastgpt/web/components/common/MyModal'));
|
||||
|
||||
@@ -173,7 +175,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
priceLabel: (
|
||||
<Flex color={'myGray.700'}>
|
||||
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
||||
{item.charsPointsPrice}
|
||||
{item.charsPointsPrice || 0}
|
||||
</Box>
|
||||
{` ${t('common:support.wallet.subscription.point')} / 60${t('common:unit.seconds')}`}
|
||||
</Flex>
|
||||
@@ -526,9 +528,10 @@ const ModelEditModal = ({
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch } = useForm<SystemModelItemType>({
|
||||
defaultValues: modelData
|
||||
});
|
||||
const { register, getValues, setValue, handleSubmit, watch, reset } =
|
||||
useForm<SystemModelItemType>({
|
||||
defaultValues: modelData
|
||||
});
|
||||
|
||||
const isCustom = !!modelData.isCustom;
|
||||
const isLLMModel = modelData?.type === ModelTypeEnum.llm;
|
||||
@@ -575,6 +578,22 @@ const ModelEditModal = ({
|
||||
}
|
||||
);
|
||||
|
||||
const [key, setKey] = useState(0);
|
||||
const { runAsync: loadDefaultConfig, loading: loadingDefaultConfig } = useRequest2(
|
||||
getSystemModelDefaultConfig,
|
||||
{
|
||||
onSuccess(res) {
|
||||
reset({
|
||||
...getValues(),
|
||||
...res
|
||||
});
|
||||
setTimeout(() => {
|
||||
setKey((prev) => prev + 1);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'modal/edit'}
|
||||
@@ -586,7 +605,7 @@ const ModelEditModal = ({
|
||||
h={'100%'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex gap={4}>
|
||||
<Flex gap={4} key={key}>
|
||||
<TableContainer flex={'1'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
@@ -1007,6 +1026,16 @@ const ModelEditModal = ({
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!modelData.isCustom && (
|
||||
<Button
|
||||
isLoading={loadingDefaultConfig}
|
||||
variant={'whiteBase'}
|
||||
mr={4}
|
||||
onClick={() => loadDefaultConfig(modelData.model)}
|
||||
>
|
||||
{t('account:reset_default')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={'whiteBase'} mr={4} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
|
||||
@@ -44,33 +44,10 @@ const UsageTableList = ({
|
||||
const { dateRange, selectTmbIds, isSelectAllTmb, usageSources, isSelectAllSource, projectName } =
|
||||
filterParams;
|
||||
const requestParams = useMemo(() => {
|
||||
const appNameMap = {
|
||||
['core.app.Question Guide']: t('common:core.app.Question Guide'),
|
||||
['common:support.wallet.usage.Audio Speech']: t('common:support.wallet.usage.Audio Speech'),
|
||||
['support.wallet.usage.Whisper']: t('common:support.wallet.usage.Whisper'),
|
||||
['support.wallet.moduleName.index']: t('common:support.wallet.moduleName.index'),
|
||||
['support.wallet.moduleName.qa']: t('common:support.wallet.moduleName.qa'),
|
||||
['core.dataset.training.Auto mode']: t('common:core.dataset.training.Auto mode'),
|
||||
['common:core.module.template.ai_chat']: t('common:core.module.template.ai_chat')
|
||||
};
|
||||
|
||||
const sourcesMap = Object.fromEntries(
|
||||
Object.entries(UsageSourceMap).map(([key, config]) => [
|
||||
key,
|
||||
{
|
||||
label: t(config.label as any)
|
||||
}
|
||||
])
|
||||
);
|
||||
const title = t('account_usage:export_title');
|
||||
|
||||
return {
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
sources: isSelectAllSource ? undefined : usageSources,
|
||||
sourcesMap,
|
||||
appNameMap,
|
||||
title,
|
||||
teamMemberIds: isSelectAllTmb ? undefined : selectTmbIds,
|
||||
projectName
|
||||
};
|
||||
@@ -81,8 +58,7 @@ const UsageTableList = ({
|
||||
isSelectAllTmb,
|
||||
projectName,
|
||||
selectTmbIds,
|
||||
usageSources,
|
||||
t
|
||||
usageSources
|
||||
]);
|
||||
|
||||
const {
|
||||
@@ -103,7 +79,29 @@ const UsageTableList = ({
|
||||
await downloadFetch({
|
||||
url: `/api/proApi/support/wallet/usage/exportUsage`,
|
||||
filename: `usage.csv`,
|
||||
body: requestParams
|
||||
body: {
|
||||
...requestParams,
|
||||
appNameMap: {
|
||||
['core.app.Question Guide']: t('common:core.app.Question Guide'),
|
||||
['common:support.wallet.usage.Audio Speech']: t(
|
||||
'common:support.wallet.usage.Audio Speech'
|
||||
),
|
||||
['support.wallet.usage.Whisper']: t('common:support.wallet.usage.Whisper'),
|
||||
['support.wallet.moduleName.index']: t('common:support.wallet.moduleName.index'),
|
||||
['support.wallet.moduleName.qa']: t('common:support.wallet.moduleName.qa'),
|
||||
['core.dataset.training.Auto mode']: t('common:core.dataset.training.Auto mode'),
|
||||
['common:core.module.template.ai_chat']: t('common:core.module.template.ai_chat')
|
||||
},
|
||||
sourcesMap: Object.fromEntries(
|
||||
Object.entries(UsageSourceMap).map(([key, config]) => [
|
||||
key,
|
||||
{
|
||||
label: t(config.label as any)
|
||||
}
|
||||
])
|
||||
),
|
||||
title: t('account_usage:export_title')
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context';
|
||||
import { getWebDefaultModel } from '@/web/common/system/utils';
|
||||
|
||||
const SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
const { llmModelList } = useSystemStore();
|
||||
@@ -21,6 +22,9 @@ const SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
}),
|
||||
[llmModelList, item.llmModelType]
|
||||
);
|
||||
const defaultModel = useMemo(() => {
|
||||
return getWebDefaultModel(modelList).model;
|
||||
}, [modelList]);
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(e: string) => {
|
||||
@@ -38,10 +42,10 @@ const SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!item.value && modelList.length > 0) {
|
||||
onChangeModel(modelList[0].model);
|
||||
if (!modelList.find((model) => model.model === item.value) && !!defaultModel) {
|
||||
onChangeModel(defaultModel);
|
||||
}
|
||||
}, []);
|
||||
}, [defaultModel, item.value, modelList, onChangeModel]);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
|
||||
@@ -69,7 +69,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
usingReRank: false,
|
||||
limit: 5000,
|
||||
similarity: 0,
|
||||
datasetSearchUsingExtensionQuery: true,
|
||||
datasetSearchUsingExtensionQuery: false,
|
||||
datasetSearchExtensionModel: defaultModels.llm?.model,
|
||||
datasetSearchExtensionBg: ''
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { datasetTypeCourseMap } from '@/web/core/dataset/constants';
|
||||
import ApiDatasetForm from '../ApiDatasetForm';
|
||||
import { getWebDefaultModel } from '@/web/common/system/utils';
|
||||
|
||||
export type CreateDatasetType =
|
||||
| DatasetTypeEnum.dataset
|
||||
@@ -79,7 +80,7 @@ const CreateModal = ({
|
||||
name: '',
|
||||
intro: '',
|
||||
vectorModel: defaultModels.embedding?.model,
|
||||
agentModel: defaultModels.llm?.model
|
||||
agentModel: getWebDefaultModel(datasetModelList)?.model
|
||||
}
|
||||
});
|
||||
const { register, setValue, handleSubmit, watch } = form;
|
||||
|
||||
@@ -38,7 +38,7 @@ async function handler(
|
||||
return TrackModel.create(data);
|
||||
}
|
||||
|
||||
export default NextAPI(useIPFrequencyLimit(1, 5), handler);
|
||||
export default NextAPI(useIPFrequencyLimit({ id: 'push-tracks', seconds: 1, limit: 5 }), handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
|
||||
22
projects/app/src/pages/api/core/ai/model/getDefaultConfig.ts
Normal file
22
projects/app/src/pages/api/core/ai/model/getDefaultConfig.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { getSystemModelConfig } from '@fastgpt/service/core/ai/config/utils';
|
||||
import { SystemModelItemType } from '@fastgpt/service/core/ai/type';
|
||||
|
||||
export type getDefaultQuery = { model: string };
|
||||
|
||||
export type getDefaultBody = {};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<getDefaultBody, getDefaultQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<SystemModelItemType> {
|
||||
await authSystemAdmin({ req });
|
||||
|
||||
const model = req.query.model;
|
||||
|
||||
return getSystemModelConfig(model);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { findModelFromAlldata } from '@fastgpt/service/core/ai/model';
|
||||
import { findModelFromAlldata, getReRankModel } from '@fastgpt/service/core/ai/model';
|
||||
import {
|
||||
EmbeddingModelItemType,
|
||||
LLMModelItemType,
|
||||
@@ -120,6 +120,7 @@ const testSTTModel = async (model: STTModelType) => {
|
||||
|
||||
const testReRankModel = async (model: ReRankModelItemType) => {
|
||||
await reRankRecall({
|
||||
model,
|
||||
query: 'Hi',
|
||||
documents: [{ id: '1', text: 'Hi' }]
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { MongoSystemModel } from '@fastgpt/service/core/ai/config/schema';
|
||||
import { loadSystemModels } from '@fastgpt/service/core/ai/config/utils';
|
||||
import { updateFastGPTConfigBuffer } from '@fastgpt/service/common/system/config/controller';
|
||||
import { ModelTypeEnum } from '@fastgpt/global/core/ai/model';
|
||||
import { authSystemAdmin } from '@fastgpt/service/support/permission/user/auth';
|
||||
|
||||
export type updateDefaultQuery = {};
|
||||
|
||||
@@ -22,10 +23,12 @@ async function handler(
|
||||
req: ApiRequestProps<updateDefaultBody, updateDefaultQuery>,
|
||||
res: ApiResponseType<any>
|
||||
): Promise<updateDefaultResponse> {
|
||||
await authSystemAdmin({ req });
|
||||
|
||||
const { llm, embedding, tts, stt, rerank } = req.body;
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoSystemModel.updateMany({}, { $set: { 'metadata.isDefault': false } }, { session });
|
||||
await MongoSystemModel.updateMany({}, { $unset: { 'metadata.isDefault': 1 } }, { session });
|
||||
|
||||
if (llm) {
|
||||
await MongoSystemModel.updateOne(
|
||||
|
||||
@@ -100,4 +100,4 @@ async function handler(req: NextApiRequest) {
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(useIPFrequencyLimit(1, 15), handler);
|
||||
export default NextAPI(useIPFrequencyLimit({ id: 'search-test', seconds: 1, limit: 15 }), handler);
|
||||
|
||||
@@ -70,4 +70,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(useIPFrequencyLimit(120, 10, true), handler);
|
||||
export default NextAPI(
|
||||
useIPFrequencyLimit({ id: 'login-by-password', seconds: 120, limit: 10, force: true }),
|
||||
handler
|
||||
);
|
||||
|
||||
@@ -90,7 +90,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
removeFilesByPaths(filePaths);
|
||||
}
|
||||
|
||||
export default NextAPI(useIPFrequencyLimit(1, 1), handler);
|
||||
export default NextAPI(
|
||||
useIPFrequencyLimit({ id: 'transcriptions', seconds: 1, limit: 1 }),
|
||||
handler
|
||||
);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { useSystemStore } from './useSystemStore';
|
||||
|
||||
export const downloadFetch = async ({
|
||||
@@ -43,5 +44,15 @@ export const downloadFetch = async ({
|
||||
|
||||
export const getWebLLMModel = (model?: string) => {
|
||||
const list = useSystemStore.getState().llmModelList;
|
||||
return list.find((item) => item.model === model || item.name === model) ?? list[0];
|
||||
const defaultModels = useSystemStore.getState().defaultModels;
|
||||
|
||||
return list.find((item) => item.model === model || item.name === model) ?? defaultModels.llm!;
|
||||
};
|
||||
export const getWebDefaultModel = (llmList: LLMModelItemType[] = []) => {
|
||||
const list = llmList.length > 0 ? llmList : useSystemStore.getState().llmModelList;
|
||||
const defaultModels = useSystemStore.getState().defaultModels;
|
||||
|
||||
return defaultModels.llm && list.find((item) => item.model === defaultModels.llm?.model)
|
||||
? defaultModels.llm
|
||||
: list[0];
|
||||
};
|
||||
|
||||
@@ -69,10 +69,6 @@ export const useAudioPlay = (
|
||||
|
||||
if (!response.body || !response.ok) {
|
||||
const data = await response.json();
|
||||
toast({
|
||||
status: 'error',
|
||||
title: getErrText(data, t('common:core.chat.Audio Speech Error'))
|
||||
});
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return response.body;
|
||||
|
||||
@@ -10,6 +10,9 @@ export const getSystemModelList = () => GET<listResponse>('/core/ai/model/list')
|
||||
export const getSystemModelDetail = (model: string) =>
|
||||
GET<SystemModelItemType>('/core/ai/model/detail', { model });
|
||||
|
||||
export const getSystemModelDefaultConfig = (model: string) =>
|
||||
GET<SystemModelItemType>('/core/ai/model/getDefaultConfig', { model });
|
||||
|
||||
export const putSystemModel = (data: updateBody) => PUT('/core/ai/model/update', data);
|
||||
|
||||
export const deleteSystemModel = (data: deleteQuery) => DELETE('/core/ai/model/delete', data);
|
||||
|
||||
Reference in New Issue
Block a user