V4.9.7 feature (#4669)
* update doc * feat: Add coupon redemption feature for team subscriptions (#4595) * feat: Add coupon redemption feature for team subscriptions - Introduced `TeamCouponSub` and `TeamCouponSchema` types - Added `redeemCoupon` API endpoint - Updated UI to include a modal for coupon redemption - Added new icon and translations for "Redeem coupon" * perf: remove field teamId * perf: use dynamic import * refactor: move to page component * perf: coupon code * perf: mcp server * perf: test * auto layout (#4634) * fix 4.9.6 (#4631) * fix debug quote list * delete next text node match * fix extract default boolean value * export latest 100 chat items * fix quote item ui * doc * fix doc * feat: auto layout * perf: auto layout * fix: auto layout null * add start node --------- Co-authored-by: heheer <heheer@sealos.io> * fix: share link (#4644) * Add workflow run duration;Get audio duration (#4645) * add duration * get audio duration * Custom config path (#4649) * feat: 通过环境变量DATA_PATH获取配置文件目录 (#4622) 通过环境变量DATA_PATH获取配置文件目录,以应对不同的部署方式的多样化需求 * feat: custom configjson path * doc --------- Co-authored-by: John Chen <sss1991@163.com> * 程序api调用场景下,如果大量调用带有图片或视频,产生的聊天记录会导致后台mongo数据库异常。这个修改给api客户端一个禁止生成聊天记录的选项,避免这个后果。 (#3964) * update special chatId * perf: vector db rename * update operationLog (#4647) * update operationLog * combine operationLogMap * solve operationI18nLogMap bug * remoce log * feat: Rerank usage (#4654) * refresh concat when update (#4655) * fix: refresh code * perf: timer lock * Fix operationLog (#4657) * perf: http streamable mcp * add alipay (#4630) * perf: subplan ui * perf: pay code * hiden bank tip * Fix: pay error (#4665) * fix quote number (#4666) * remove log --------- Co-authored-by: a.e. <49438478+I-Info@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: John Chen <sss1991@163.com> Co-authored-by: gaord <bengao168@msn.com> Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,8 @@ import {
|
||||
PopoverBody,
|
||||
PopoverArrow,
|
||||
Box,
|
||||
Flex
|
||||
Flex,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
@@ -32,6 +33,8 @@ const A = ({ children, ...props }: any) => {
|
||||
manual: true
|
||||
});
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
// empty href link
|
||||
if (!props.href && typeof children?.[0] === 'string') {
|
||||
const text = useMemo(() => String(children), [children]);
|
||||
@@ -71,77 +74,95 @@ const A = ({ children, ...props }: any) => {
|
||||
direction="rtl"
|
||||
placement="bottom"
|
||||
strategy={'fixed'}
|
||||
onOpen={() => runAsync(String(children))}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onOpen={() => {
|
||||
onOpen();
|
||||
runAsync(String(children));
|
||||
}}
|
||||
gutter={4}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Button variant={'unstyled'} minH={0} minW={0} h={'auto'}>
|
||||
<MyTooltip label={t('common:read_quote')}>
|
||||
<Box
|
||||
w={5}
|
||||
h={5}
|
||||
border={'1px solid'}
|
||||
w={'14px'}
|
||||
h={'14px'}
|
||||
borderRadius={'full'}
|
||||
borderColor={'myGray.200'}
|
||||
color={'myGray.500'}
|
||||
bg={'rgba(0, 0, 0, 0.08)'}
|
||||
color={'myGray.600'}
|
||||
fontSize={'10px'}
|
||||
display={'flex'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
ml={0.5}
|
||||
transform={'translateY(-2px)'}
|
||||
transform={'translateY(-3px)'}
|
||||
>
|
||||
{index}
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent boxShadow={'lg'} w={'400px'} py={4}>
|
||||
<MyBox isLoading={loading} minH={'224px'}>
|
||||
<PopoverContent boxShadow={'lg'} w={'500px'} py={4}>
|
||||
<MyBox isLoading={loading}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody
|
||||
px={4}
|
||||
py={0}
|
||||
fontSize={'sm'}
|
||||
maxW={'400px'}
|
||||
maxH={'224px'}
|
||||
overflow={'auto'}
|
||||
>
|
||||
<Box
|
||||
alignItems={'center'}
|
||||
fontSize={'xs'}
|
||||
border={'sm'}
|
||||
borderRadius={'sm'}
|
||||
overflow={'hidden'}
|
||||
display={'inline-flex'}
|
||||
height={6}
|
||||
>
|
||||
<Flex
|
||||
color={'myGray.500'}
|
||||
bg={'myGray.150'}
|
||||
w={4}
|
||||
justifyContent={'center'}
|
||||
fontSize={'10px'}
|
||||
h={'full'}
|
||||
<PopoverBody py={0} px={0} fontSize={'sm'}>
|
||||
<Flex px={4} pb={1} justifyContent={'space-between'}>
|
||||
<Box
|
||||
alignItems={'center'}
|
||||
fontSize={'xs'}
|
||||
border={'sm'}
|
||||
borderRadius={'sm'}
|
||||
overflow={'hidden'}
|
||||
display={'inline-flex'}
|
||||
height={6}
|
||||
mr={1}
|
||||
flexShrink={0}
|
||||
>
|
||||
{index}
|
||||
</Flex>
|
||||
<Flex px={1.5}>
|
||||
<MyIcon name={icon as any} mr={1} flexShrink={0} w={'12px'} />
|
||||
<Box
|
||||
className={'textEllipsis'}
|
||||
wordBreak={'break-all'}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'mini'}
|
||||
color={'myGray.900'}
|
||||
<Flex
|
||||
color={'myGray.500'}
|
||||
bg={'myGray.150'}
|
||||
w={4}
|
||||
justifyContent={'center'}
|
||||
fontSize={'10px'}
|
||||
h={'full'}
|
||||
alignItems={'center'}
|
||||
mr={1}
|
||||
flexShrink={0}
|
||||
>
|
||||
{sourceData.sourceName}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box>
|
||||
{index}
|
||||
</Flex>
|
||||
<Flex px={1.5}>
|
||||
<MyIcon name={icon as any} mr={1} flexShrink={0} w={'12px'} />
|
||||
<Box
|
||||
className={'textEllipsis'}
|
||||
wordBreak={'break-all'}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'mini'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{sourceData.sourceName}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
color={'primary.600'}
|
||||
size={'xs'}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
eventBus.emit(EventNameEnum.openQuoteReader, {
|
||||
// quoteId: String(children),
|
||||
sourceId: sourceData.sourceId,
|
||||
sourceName: sourceData.sourceName,
|
||||
datasetId: quoteData?.collection.datasetId,
|
||||
collectionId: quoteData?.collection._id
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('common:all_quotes')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box h={'300px'} overflow={'auto'} px={4}>
|
||||
<Markdown source={quoteData?.q} />
|
||||
{quoteData?.a && <Markdown source={quoteData?.a} />}
|
||||
</Box>
|
||||
|
||||
@@ -94,6 +94,7 @@ const ModelTable = () => {
|
||||
typeLabel: t('common:model.type.embedding'),
|
||||
priceLabel: (
|
||||
<Flex color={'myGray.700'}>
|
||||
{`${t('common:common.Input')}: `}
|
||||
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
||||
{item.charsPointsPrice || 0}
|
||||
</Box>
|
||||
@@ -131,7 +132,17 @@ const ModelTable = () => {
|
||||
const formatRerankModelList = reRankModelList.map((item) => ({
|
||||
...item,
|
||||
typeLabel: t('common:model.type.reRank'),
|
||||
priceLabel: <Flex color={'myGray.700'}>- </Flex>,
|
||||
priceLabel: item.charsPointsPrice ? (
|
||||
<Flex color={'myGray.700'}>
|
||||
{`${t('common:common.Input')}: `}
|
||||
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
||||
{item.charsPointsPrice}
|
||||
</Box>
|
||||
{` ${t('common:support.wallet.subscription.point')} / 1K Tokens`}
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
tagColor: 'red'
|
||||
}));
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ const DatasetParamsModal = ({
|
||||
defaultValues: {
|
||||
searchMode,
|
||||
embeddingWeight: embeddingWeight || 0.5,
|
||||
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
|
||||
usingReRank: usingReRank || true,
|
||||
rerankModel: rerankModel || defaultModels?.rerank?.model,
|
||||
rerankWeight: rerankWeight || 0.5,
|
||||
limit,
|
||||
@@ -246,11 +246,6 @@ const DatasetParamsModal = ({
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
{t('common:core.ai.Not deploy rerank model')}
|
||||
</Box>
|
||||
) : teamPlanStatus?.standardConstants &&
|
||||
!teamPlanStatus?.standardConstants?.permissionReRank ? (
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
{t('common:support.team.limit.No permission rerank')}
|
||||
</Box>
|
||||
) : (
|
||||
<Switch {...register('usingReRank')} />
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Box, BoxProps, Card, Flex } from '@chakra-ui/react';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import ChatController, { type ChatControllerProps } from './ChatController';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
@@ -26,6 +26,8 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
|
||||
import dayjs from 'dayjs';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||
import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils';
|
||||
|
||||
const colorMap = {
|
||||
[ChatStatusEnum.loading]: {
|
||||
@@ -141,6 +143,18 @@ const ChatItem = (props: Props) => {
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
|
||||
const showNodeStatus = useContextSelector(ChatItemContext, (v) => v.showNodeStatus);
|
||||
|
||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
const isShowReadRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
|
||||
const { totalQuoteList: quoteList = [] } = useMemo(
|
||||
() => addStatisticalDataToHistoryItem(chat),
|
||||
[chat]
|
||||
);
|
||||
|
||||
const isChatLog = chatType === 'log';
|
||||
|
||||
const { copyData } = useCopyData();
|
||||
@@ -208,6 +222,59 @@ const ChatItem = (props: Props) => {
|
||||
return groupedValues;
|
||||
}, [chat.obj, chat.value, isChatting]);
|
||||
|
||||
const handleOpenQuoteReader = useCallback(
|
||||
({
|
||||
collectionId,
|
||||
sourceId,
|
||||
sourceName,
|
||||
datasetId
|
||||
}: {
|
||||
collectionId?: string;
|
||||
sourceId?: string;
|
||||
sourceName?: string;
|
||||
datasetId?: string;
|
||||
}) => {
|
||||
if (!setQuoteData) return;
|
||||
|
||||
const collectionIdList = collectionId
|
||||
? [collectionId]
|
||||
: [...new Set(quoteList.map((item) => item.collectionId))];
|
||||
|
||||
setQuoteData({
|
||||
rawSearch: quoteList,
|
||||
metadata:
|
||||
collectionId && isShowReadRawSource
|
||||
? {
|
||||
appId: appId,
|
||||
chatId: chatId,
|
||||
chatItemDataId: chat.dataId,
|
||||
collectionId: collectionId,
|
||||
sourceId: sourceId || '',
|
||||
sourceName: sourceName || '',
|
||||
datasetId: datasetId || '',
|
||||
outLinkAuthData
|
||||
}
|
||||
: {
|
||||
appId: appId,
|
||||
chatId: chatId,
|
||||
chatItemDataId: chat.dataId,
|
||||
collectionIdList,
|
||||
sourceId: sourceId,
|
||||
sourceName: sourceName,
|
||||
outLinkAuthData
|
||||
}
|
||||
});
|
||||
},
|
||||
[setQuoteData, quoteList, isShowReadRawSource, appId, chatId, chat.dataId, outLinkAuthData]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on(EventNameEnum.openQuoteReader, handleOpenQuoteReader);
|
||||
return () => {
|
||||
eventBus.off(EventNameEnum.openQuoteReader);
|
||||
};
|
||||
}, [handleOpenQuoteReader]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
_hover={{
|
||||
|
||||
@@ -34,17 +34,19 @@ const QuoteList = React.memo(function QuoteList({
|
||||
|
||||
const { data: quoteList } = useRequest2(
|
||||
async () =>
|
||||
await getQuoteDataList({
|
||||
datasetDataIdList: rawSearch.map((item) => item.id),
|
||||
collectionIdList: [...new Set(rawSearch.map((item) => item.collectionId))],
|
||||
chatItemDataId,
|
||||
appId,
|
||||
chatId: RawSourceBoxProps.chatId,
|
||||
...outLinkAuthData
|
||||
}),
|
||||
!!chatItemDataId
|
||||
? await getQuoteDataList({
|
||||
datasetDataIdList: rawSearch.map((item) => item.id),
|
||||
collectionIdList: [...new Set(rawSearch.map((item) => item.collectionId))],
|
||||
chatItemDataId,
|
||||
appId,
|
||||
chatId: RawSourceBoxProps.chatId,
|
||||
...outLinkAuthData
|
||||
})
|
||||
: [],
|
||||
{
|
||||
refreshDeps: [rawSearch, RawSourceBoxProps.chatId],
|
||||
manual: !chatItemDataId
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils';
|
||||
import { useSize } from 'ahooks';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||
|
||||
const ContextModal = dynamic(() => import('./ContextModal'));
|
||||
const WholeResponseModal = dynamic(() => import('../../../components/WholeResponseModal'));
|
||||
@@ -30,23 +30,18 @@ const ResponseTags = ({
|
||||
const { t } = useTranslation();
|
||||
const quoteListRef = React.useRef<HTMLDivElement>(null);
|
||||
const dataId = historyItem.dataId;
|
||||
const chatTime = historyItem.time || new Date();
|
||||
|
||||
const chatTime = historyItem.time || new Date();
|
||||
const durationSeconds = historyItem.durationSeconds || 0;
|
||||
const {
|
||||
totalQuoteList: quoteList = [],
|
||||
llmModuleAccount = 0,
|
||||
totalRunningTime: runningTime = 0,
|
||||
historyPreviewLength = 0
|
||||
} = useMemo(() => addStatisticalDataToHistoryItem(historyItem), [historyItem]);
|
||||
|
||||
const [quoteFolded, setQuoteFolded] = useState<boolean>(true);
|
||||
|
||||
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
|
||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||
|
||||
const notSharePage = useMemo(() => chatType !== 'share', [chatType]);
|
||||
|
||||
@@ -66,7 +61,6 @@ const ResponseTags = ({
|
||||
? quoteListRef.current.scrollHeight > (isPc ? 50 : 55)
|
||||
: true;
|
||||
|
||||
const isShowReadRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const sourceList = useMemo(() => {
|
||||
return Object.values(
|
||||
quoteList.reduce((acc: Record<string, SearchDataResponseItemType[]>, cur) => {
|
||||
@@ -86,11 +80,20 @@ const ResponseTags = ({
|
||||
}));
|
||||
}, [quoteList]);
|
||||
|
||||
const openQuoteReader = (item?: {
|
||||
collectionId?: string;
|
||||
sourceId?: string;
|
||||
sourceName?: string;
|
||||
datasetId?: string;
|
||||
}) => {
|
||||
eventBus.emit(EventNameEnum.openQuoteReader, item);
|
||||
};
|
||||
|
||||
const notEmptyTags =
|
||||
quoteList.length > 0 ||
|
||||
(llmModuleAccount === 1 && notSharePage) ||
|
||||
(llmModuleAccount > 1 && notSharePage) ||
|
||||
(isPc && runningTime > 0) ||
|
||||
(isPc && durationSeconds > 0) ||
|
||||
notSharePage;
|
||||
|
||||
return !showTags ? null : (
|
||||
@@ -158,35 +161,7 @@ const ResponseTags = ({
|
||||
cursor={'pointer'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (isShowReadRawSource) {
|
||||
setQuoteData({
|
||||
rawSearch: quoteList,
|
||||
metadata: {
|
||||
appId,
|
||||
chatId,
|
||||
chatItemDataId: dataId,
|
||||
collectionId: item.collectionId,
|
||||
sourceId: item.sourceId || '',
|
||||
sourceName: item.sourceName,
|
||||
datasetId: item.datasetId,
|
||||
outLinkAuthData
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setQuoteData({
|
||||
rawSearch: quoteList,
|
||||
metadata: {
|
||||
appId,
|
||||
chatId,
|
||||
chatItemDataId: dataId,
|
||||
collectionIdList: [item.collectionId],
|
||||
sourceId: item.sourceId || '',
|
||||
sourceName: item.sourceName,
|
||||
outLinkAuthData
|
||||
}
|
||||
});
|
||||
}
|
||||
openQuoteReader(item);
|
||||
}}
|
||||
height={6}
|
||||
>
|
||||
@@ -241,17 +216,7 @@ const ResponseTags = ({
|
||||
cursor={'pointer'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
setQuoteData({
|
||||
rawSearch: quoteList,
|
||||
metadata: {
|
||||
appId,
|
||||
chatId,
|
||||
chatItemDataId: dataId,
|
||||
collectionIdList: [...new Set(quoteList.map((item) => item.collectionId))],
|
||||
outLinkAuthData
|
||||
}
|
||||
});
|
||||
openQuoteReader();
|
||||
}}
|
||||
>
|
||||
{t('chat:citations', { num: quoteList.length })}
|
||||
@@ -279,10 +244,10 @@ const ResponseTags = ({
|
||||
{t('chat:multiple_AI_conversations')}
|
||||
</MyTag>
|
||||
)}
|
||||
{isPc && runningTime > 0 && (
|
||||
{isPc && durationSeconds > 0 && (
|
||||
<MyTooltip label={t('chat:module_runtime_and')}>
|
||||
<MyTag colorSchema="purple" type="borderSolid" cursor={'default'}>
|
||||
{runningTime}s
|
||||
{durationSeconds.toFixed(2)}s
|
||||
</MyTag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
|
||||
@@ -221,7 +221,8 @@ const ChatBox = ({
|
||||
interactive,
|
||||
autoTTSResponse,
|
||||
variables,
|
||||
nodeResponse
|
||||
nodeResponse,
|
||||
durationSeconds
|
||||
}: generatingMessageProps & { autoTTSResponse?: boolean }) => {
|
||||
setChatRecords((state) =>
|
||||
state.map((item, index) => {
|
||||
@@ -342,6 +343,13 @@ const ChatBox = ({
|
||||
...item,
|
||||
value: item.value.concat(val)
|
||||
};
|
||||
} else if (event === SseResponseEventEnum.workflowDuration && durationSeconds) {
|
||||
return {
|
||||
...item,
|
||||
durationSeconds: item.durationSeconds
|
||||
? +(item.durationSeconds + durationSeconds).toFixed(2)
|
||||
: durationSeconds
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
@@ -17,6 +17,7 @@ export type generatingMessageProps = {
|
||||
interactive?: WorkflowInteractiveResponseType;
|
||||
variables?: Record<string, any>;
|
||||
nodeResponse?: ChatHistoryItemResType;
|
||||
durationSeconds?: number;
|
||||
};
|
||||
|
||||
export type StartChatFnProps = {
|
||||
|
||||
@@ -252,29 +252,39 @@ export const WholeResponseContent = ({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Row
|
||||
label={t('common:core.chat.response.module similarity')}
|
||||
value={activeModule?.similarity}
|
||||
/>
|
||||
<Row label={t('common:core.chat.response.module limit')} value={activeModule?.limit} />
|
||||
<Row label={t('chat:response_embedding_model')} value={activeModule?.embeddingModel} />
|
||||
<Row
|
||||
label={t('chat:response_embedding_model_tokens')}
|
||||
value={`${activeModule?.embeddingTokens}`}
|
||||
/>
|
||||
{activeModule?.searchUsingReRank !== undefined && (
|
||||
<Row
|
||||
label={t('common:core.chat.response.search using reRank')}
|
||||
rawDom={
|
||||
<Box border={'base'} borderRadius={'md'} p={2}>
|
||||
{activeModule?.searchUsingReRank ? (
|
||||
activeModule?.rerankModel ? (
|
||||
<Box>{`${activeModule.rerankModel}: ${activeModule.rerankWeight}`}</Box>
|
||||
<>
|
||||
<Row
|
||||
label={t('common:core.chat.response.search using reRank')}
|
||||
rawDom={
|
||||
<Box border={'base'} borderRadius={'md'} p={2}>
|
||||
{activeModule?.searchUsingReRank ? (
|
||||
activeModule?.rerankModel ? (
|
||||
<Box>{`${activeModule.rerankModel}: ${activeModule.rerankWeight}`}</Box>
|
||||
) : (
|
||||
'True'
|
||||
)
|
||||
) : (
|
||||
'True'
|
||||
)
|
||||
) : (
|
||||
`False`
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
`False`
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<Row
|
||||
label={t('chat:response_rerank_tokens')}
|
||||
value={`${activeModule?.reRankInputTokens}`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{activeModule.queryExtensionResult && (
|
||||
<>
|
||||
|
||||
@@ -17,9 +17,11 @@ type FormType = {
|
||||
|
||||
const UpdateContactModal = ({
|
||||
onClose,
|
||||
onSuccess,
|
||||
mode
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onSuccess?: (val: string) => void;
|
||||
mode: 'contact' | 'notification_account';
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -37,20 +39,22 @@ const UpdateContactModal = ({
|
||||
const verifyCode = watch('verifyCode');
|
||||
|
||||
const { runAsync: onSubmit, loading: isLoading } = useRequest2(
|
||||
(data: FormType) => {
|
||||
async (data: FormType) => {
|
||||
if (mode === 'contact') {
|
||||
return updateContact(data);
|
||||
await updateContact(data);
|
||||
} else {
|
||||
return updateNotificationAccount({
|
||||
await updateNotificationAccount({
|
||||
account: data.contact,
|
||||
verifyCode: data.verifyCode
|
||||
});
|
||||
}
|
||||
return data.contact;
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
onSuccess(data) {
|
||||
initUserInfo();
|
||||
onClose();
|
||||
onSuccess?.(data);
|
||||
},
|
||||
successToast: t('common:support.user.info.bind_notification_success'),
|
||||
errorToast: t('common:support.user.info.bind_notification_error')
|
||||
|
||||
@@ -1,36 +1,98 @@
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
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 { Box, ModalBody, Flex, Button } from '@chakra-ui/react';
|
||||
import { checkBalancePayResult, putUpdatePayment } from '@/web/support/wallet/bill/api';
|
||||
import LightTip from '@fastgpt/web/components/common/LightTip';
|
||||
import QRCode from 'qrcode';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import {
|
||||
BillPayWayEnum,
|
||||
BillStatusEnum,
|
||||
QR_CODE_SIZE
|
||||
} from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { CreateBillResponse } from '@fastgpt/global/support/wallet/bill/api';
|
||||
|
||||
export type QRPayProps = {
|
||||
readPrice: number;
|
||||
codeUrl: string;
|
||||
billId: string;
|
||||
export type QRPayProps = CreateBillResponse & {
|
||||
tip?: string;
|
||||
};
|
||||
|
||||
const qrCodeSize = 168;
|
||||
|
||||
const QRCodePayModal = ({
|
||||
tip,
|
||||
readPrice,
|
||||
codeUrl,
|
||||
billId,
|
||||
payment,
|
||||
qrCode,
|
||||
iframeCode,
|
||||
markdown,
|
||||
onSuccess
|
||||
}: QRPayProps & { tip?: string; onSuccess?: () => any }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const canvasRef = useRef<HTMLDivElement>(null);
|
||||
const toast = useToast();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const isAlipayConfigured = feConfigs.payConfig?.alipay;
|
||||
const isWxConfigured = feConfigs.payConfig?.wx;
|
||||
const isBankConfigured = feConfigs.payConfig?.bank;
|
||||
|
||||
const [payWayRenderData, setPayWayRenderData] = useState<{
|
||||
qrCode?: string;
|
||||
iframeCode?: string;
|
||||
markdown?: string;
|
||||
}>({
|
||||
qrCode,
|
||||
iframeCode,
|
||||
markdown
|
||||
});
|
||||
|
||||
const [selectedPayment, setSelectedPayment] = useState(payment);
|
||||
const { runAsync: handlePaymentChange, loading: isUpdating } = useRequest2(
|
||||
async (newPayment: BillPayWayEnum) => {
|
||||
if (newPayment === selectedPayment) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await putUpdatePayment({ billId, payWay: newPayment });
|
||||
setPayWayRenderData(response);
|
||||
setSelectedPayment(newPayment);
|
||||
},
|
||||
{
|
||||
refreshDeps: [billId, selectedPayment]
|
||||
}
|
||||
);
|
||||
|
||||
// Check pay result
|
||||
useRequest2(() => checkBalancePayResult(billId), {
|
||||
manual: false,
|
||||
pollingInterval: 2000,
|
||||
onSuccess: ({ status, description }) => {
|
||||
if (status === BillStatusEnum.SUCCESS) {
|
||||
toast.toast({
|
||||
description: t('common:pay_success'),
|
||||
status: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
onSuccess?.();
|
||||
} else {
|
||||
console.log(status, description);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// UI render
|
||||
// Draw QR code
|
||||
const drawCode = useCallback(() => {
|
||||
if (!payWayRenderData.qrCode) return;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
QRCode.toCanvas(canvas, codeUrl, {
|
||||
width: qrCodeSize,
|
||||
|
||||
QRCode.toCanvas(canvas, payWayRenderData.qrCode, {
|
||||
width: QR_CODE_SIZE,
|
||||
margin: 0,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
@@ -41,38 +103,125 @@ const QRCodePayModal = ({
|
||||
if (canvasRef.current) {
|
||||
canvasRef.current.innerHTML = '';
|
||||
canvasRef.current.appendChild(canvas);
|
||||
} else {
|
||||
drawCode();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('QRCode generation error:', err);
|
||||
});
|
||||
}, [codeUrl]);
|
||||
|
||||
.catch(console.error);
|
||||
}, [payWayRenderData.qrCode]);
|
||||
useEffect(() => {
|
||||
drawCode();
|
||||
}, [drawCode]);
|
||||
|
||||
useRequest2(() => checkBalancePayResult(billId), {
|
||||
manual: false,
|
||||
pollingInterval: 2000,
|
||||
onSuccess: (res) => {
|
||||
if (res) {
|
||||
onSuccess?.();
|
||||
// Payment Button
|
||||
const getPaymentButtonStyles = (isActive: boolean) => ({
|
||||
baseStyle: {
|
||||
display: 'flex',
|
||||
padding: '13px 22px 13px 19px',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flex: '1 0 0',
|
||||
borderRadius: '7.152px',
|
||||
border: isActive ? '1px solid #3370FF' : '1px solid #E8EBF0',
|
||||
background: '#FFF',
|
||||
_hover: {
|
||||
background: isActive ? '#FFF' : '#F7F8FA',
|
||||
border: isActive ? '1px solid #3370FF' : '1px solid #E8EBF0'
|
||||
},
|
||||
_active: {
|
||||
background: '#FFF',
|
||||
borderColor: '#3370FF'
|
||||
}
|
||||
},
|
||||
errorToast: ''
|
||||
}
|
||||
});
|
||||
const renderPaymentContent = () => {
|
||||
if (payWayRenderData.qrCode) {
|
||||
return <Box ref={canvasRef} display={'inline-block'} h={`${QR_CODE_SIZE}px`} />;
|
||||
}
|
||||
if (payWayRenderData.iframeCode) {
|
||||
return (
|
||||
<iframe
|
||||
srcDoc={payWayRenderData.iframeCode}
|
||||
style={{
|
||||
width: QR_CODE_SIZE + 5,
|
||||
height: QR_CODE_SIZE + 5,
|
||||
border: 'none',
|
||||
display: 'inline-block'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (payWayRenderData.markdown) {
|
||||
return <Markdown source={payWayRenderData.markdown} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<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 })}
|
||||
<MyModal
|
||||
isLoading={isUpdating}
|
||||
isOpen
|
||||
title={t('common:user.Pay')}
|
||||
iconSrc="/imgs/modal/wallet.svg"
|
||||
w={'600px'}
|
||||
>
|
||||
<ModalBody textAlign={'center'} padding={['16px 24px', '32px 52px']}>
|
||||
{tip && <LightTip text={tip} mb={6} textAlign={'left'} />}
|
||||
<Box>{t('common:pay_money')}</Box>
|
||||
<Box color="primary.600" fontSize="32px" fontWeight="600" lineHeight="40px" mb={6}>
|
||||
¥{readPrice.toFixed(2)}
|
||||
</Box>
|
||||
|
||||
{renderPaymentContent()}
|
||||
|
||||
{selectedPayment !== BillPayWayEnum.bank && (
|
||||
<Box
|
||||
mt={5}
|
||||
textAlign={'center'}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
gap={1}
|
||||
>
|
||||
<MyIcon name={'common/info'} w={4} h={4} />
|
||||
{t('common:pay.noclose')}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Flex justifyContent="center" gap={3} mt={6}>
|
||||
{isWxConfigured && (
|
||||
<Button
|
||||
flex={1}
|
||||
h={10}
|
||||
onClick={() => handlePaymentChange(BillPayWayEnum.wx)}
|
||||
color={'myGray.900'}
|
||||
leftIcon={<MyIcon name={'common/wechat'} />}
|
||||
sx={getPaymentButtonStyles(selectedPayment === BillPayWayEnum.wx).baseStyle}
|
||||
>
|
||||
{t('common:pay.wx_payment')}
|
||||
</Button>
|
||||
)}
|
||||
{isAlipayConfigured && (
|
||||
<Button
|
||||
flex={1}
|
||||
h={10}
|
||||
color={'myGray.900'}
|
||||
onClick={() => handlePaymentChange(BillPayWayEnum.alipay)}
|
||||
leftIcon={<MyIcon name={'common/alipay'} />}
|
||||
sx={getPaymentButtonStyles(selectedPayment === BillPayWayEnum.alipay).baseStyle}
|
||||
>
|
||||
{t('common:pay_alipay_payment')}
|
||||
</Button>
|
||||
)}
|
||||
{isBankConfigured && (
|
||||
<Button
|
||||
flex={1}
|
||||
h={10}
|
||||
color={'myGray.900'}
|
||||
onClick={() => handlePaymentChange(BillPayWayEnum.bank)}
|
||||
sx={getPaymentButtonStyles(selectedPayment === BillPayWayEnum.bank).baseStyle}
|
||||
>
|
||||
{t('common:pay_corporate_payment')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
|
||||
@@ -38,9 +38,9 @@ const StandardPlanContentList = ({
|
||||
permissionCustomApiKey: plan.permissionCustomApiKey,
|
||||
permissionCustomCopyright: plan.permissionCustomCopyright,
|
||||
trainingWeight: plan.trainingWeight,
|
||||
permissionReRank: plan.permissionReRank,
|
||||
totalPoints: plan.totalPoints * (mode === SubModeEnum.month ? 1 : 12),
|
||||
permissionWebsiteSync: plan.permissionWebsiteSync
|
||||
permissionWebsiteSync: plan.permissionWebsiteSync,
|
||||
permissionTeamOperationLog: plan.permissionTeamOperationLog
|
||||
};
|
||||
}, [subPlans?.standard, level, mode]);
|
||||
|
||||
@@ -113,18 +113,20 @@ const StandardPlanContentList = ({
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
{!!planContent.permissionReRank && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>{t('common:support.wallet.subscription.rerank')}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!planContent.permissionWebsiteSync && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>{t('common:support.wallet.subscription.web_site_sync')}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!planContent.permissionTeamOperationLog && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('common:support.wallet.subscription.team_operation_log')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Grid>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -45,9 +45,6 @@ export function addStatisticalDataToHistoryItem(historyItem: ChatItemType) {
|
||||
.map((item) => item.quoteList)
|
||||
.flat()
|
||||
.filter(Boolean) as SearchDataResponseItemType[],
|
||||
totalRunningTime: Number(
|
||||
historyItem.responseData?.reduce((sum, item) => sum + (item.runningTime || 0), 0).toFixed(2)
|
||||
),
|
||||
historyPreviewLength: flatResData.find(isLLMNode)?.historyPreview?.length
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function register() {
|
||||
import('@fastgpt/service/common/mongo/index'),
|
||||
import('@fastgpt/service/common/system/tools'),
|
||||
import('@/service/common/system'),
|
||||
import('@fastgpt/service/common/vectorStore/controller'),
|
||||
import('@fastgpt/service/common/vectorDB/controller'),
|
||||
import('@/service/mongo'),
|
||||
import('@/service/core/app/plugin'),
|
||||
import('@/service/common/system/volumnMongoWatch'),
|
||||
|
||||
@@ -20,13 +20,14 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
BillStatusEnum,
|
||||
BillTypeEnum,
|
||||
billPayWayMap,
|
||||
billStatusMap,
|
||||
billTypeMap
|
||||
} from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
@@ -68,39 +69,33 @@ const BillTable = () => {
|
||||
defaultRequest: false
|
||||
});
|
||||
|
||||
const { mutate: handleRefreshPayOrder, isLoading: isRefreshing } = useRequest({
|
||||
mutationFn: async (payId: string) => {
|
||||
try {
|
||||
const data = await checkBalancePayResult(payId);
|
||||
const { runAsync: handleRefreshPayOrder, loading: isRefreshing } = useRequest2(
|
||||
async (payId: string) => {
|
||||
const { status, description } = await checkBalancePayResult(payId);
|
||||
if (status === BillStatusEnum.SUCCESS) {
|
||||
toast({
|
||||
title: data,
|
||||
title: t('common:pay_success'),
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error: any) {
|
||||
} else {
|
||||
toast({
|
||||
title: error?.message,
|
||||
title: t(description as any),
|
||||
status: 'warning'
|
||||
});
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
|
||||
if (status === BillStatusEnum.SUCCESS || status === BillStatusEnum.CLOSED) {
|
||||
getData(1);
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getData(1);
|
||||
}, [billType]);
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
isLoading={isLoading || isRefreshing}
|
||||
position={'relative'}
|
||||
h={'100%'}
|
||||
minH={'50vh'}
|
||||
overflow={'overlay'}
|
||||
>
|
||||
<MyBox isLoading={isLoading} position={'relative'} h={'100%'} minH={'50vh'}>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
@@ -135,7 +130,12 @@ const BillTable = () => {
|
||||
<Td>{t(billStatusMap[item.status]?.label as any)}</Td>
|
||||
<Td>
|
||||
{item.status === 'NOTPAY' && (
|
||||
<Button mr={4} onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
|
||||
<Button
|
||||
isLoading={isRefreshing}
|
||||
mr={4}
|
||||
onClick={() => handleRefreshPayOrder(item._id)}
|
||||
size={'sm'}
|
||||
>
|
||||
{t('account_bill:update')}
|
||||
</Button>
|
||||
)}
|
||||
@@ -210,11 +210,13 @@ function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: ()
|
||||
<Box>{t(billPayWayMap[bill.metadata.payWay]?.label as any)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<FormLabel flex={'0 0 120px'}>{t('account_bill:support_wallet_amount')}:</FormLabel>
|
||||
<Box>{t('account_bill:yuan', { amount: formatStorePrice2Read(bill.price) })}</Box>
|
||||
</Flex>
|
||||
{bill.metadata && (
|
||||
{!!bill.price && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<FormLabel flex={'0 0 120px'}>{t('account_bill:support_wallet_amount')}:</FormLabel>
|
||||
<Box>{t('account_bill:yuan', { amount: formatStorePrice2Read(bill.price) })}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{bill.metadata && !!bill.price && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<FormLabel flex={'0 0 120px'}>{t('account_bill:has_invoice')}:</FormLabel>
|
||||
{bill.metadata.payWay === 'balance' ? (
|
||||
@@ -239,7 +241,7 @@ function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: ()
|
||||
{bill.metadata?.month !== undefined && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<FormLabel flex={'0 0 120px'}>{t('account_bill:subscription_mode_month')}:</FormLabel>
|
||||
<Box>{bill.metadata?.month}</Box>
|
||||
<Box>{`${bill.metadata?.month} ${t('account_bill:month')}`}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{bill.metadata?.datasetSize !== undefined && (
|
||||
|
||||
@@ -221,7 +221,7 @@ const InvoiceHeaderForm = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyBox isLoading={isLoading} pt={['1rem', '3.5rem']}>
|
||||
<MyBox isLoading={isLoading} pt={'1rem'}>
|
||||
<Flex w={'100%'} overflow={'auto'} justify={'center'} flexDir={'column'} align={'center'}>
|
||||
<InvoiceHeaderSingleForm inputForm={inputForm} />
|
||||
<Flex w={'100%'} justify={'center'} mt={'3rem'}>
|
||||
|
||||
@@ -22,6 +22,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
|
||||
const InvoiceTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const [invoiceDetailData, setInvoiceDetailData] = useState<InvoiceSchemaType | ''>('');
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { redeemCoupon } from '@/web/support/user/team/api';
|
||||
import { Button, Input, VStack, Text, ModalBody, Box, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const RedeemCouponModal = ({
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [couponCode, setCouponCode] = React.useState('');
|
||||
|
||||
const { runAsync: redeemCouponAsync, loading } = useRequest2(redeemCoupon, {
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Success')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="support/account/coupon"
|
||||
title={t('account_info:redeem_coupon')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box fontWeight={500} color={'myGray.900'} mb={'1'}>
|
||||
{t('account_info:redeem_coupon')}
|
||||
</Box>
|
||||
<Input
|
||||
placeholder={t('account_info:redeem_coupon')}
|
||||
value={couponCode}
|
||||
onChange={(e) => setCouponCode(e.target.value)}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('account_info:cancel')}
|
||||
</Button>
|
||||
<Button ml={2} isLoading={loading} onClick={() => redeemCouponAsync(couponCode)}>
|
||||
{t('account_info:confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default RedeemCouponModal;
|
||||
@@ -127,11 +127,11 @@ export const ModelEditModal = ({
|
||||
);
|
||||
|
||||
const priceUnit = useMemo(() => {
|
||||
if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens';
|
||||
if (isLLMModel || isEmbeddingModel || isRerankModel) return '/ 1k Tokens';
|
||||
if (isTTSModel) return `/ 1k ${t('common:unit.character')}`;
|
||||
if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`;
|
||||
return '';
|
||||
}, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]);
|
||||
}, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel, isRerankModel]);
|
||||
|
||||
const { runAsync: updateModel, loading: updatingModel } = useRequest2(
|
||||
async (data: SystemModelItemType) => {
|
||||
|
||||
@@ -141,6 +141,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
typeLabel: t('common:model.type.embedding'),
|
||||
priceLabel: (
|
||||
<Flex color={'myGray.700'}>
|
||||
{`${t('common:common.Input')}: `}
|
||||
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
||||
{item.charsPointsPrice || 0}
|
||||
</Box>
|
||||
@@ -184,7 +185,17 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
.map((item) => ({
|
||||
...item,
|
||||
typeLabel: t('common:model.type.reRank'),
|
||||
priceLabel: <Flex color={'myGray.700'}>- </Flex>,
|
||||
priceLabel: item.charsPointsPrice ? (
|
||||
<Flex color={'myGray.700'}>
|
||||
{`${t('common:common.Input')}: `}
|
||||
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
||||
{item.charsPointsPrice}
|
||||
</Box>
|
||||
{` ${t('common:support.wallet.subscription.point')} / 1K Tokens`}
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
tagColor: 'red'
|
||||
}));
|
||||
|
||||
|
||||
@@ -190,8 +190,9 @@ function EditModal({
|
||||
/>
|
||||
{isOpenContact && (
|
||||
<UpdateContact
|
||||
onClose={() => {
|
||||
onCloseContact();
|
||||
onClose={onCloseContact}
|
||||
onSuccess={(val) => {
|
||||
setValue('notificationAccount', val);
|
||||
}}
|
||||
mode="notification_account"
|
||||
/>
|
||||
|
||||
@@ -17,7 +17,7 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { getOperationLogs } from '@/web/support/user/team/operantionLog/api';
|
||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||
import { operationLogI18nMap } from '@fastgpt/service/support/operationLog/constants';
|
||||
import { operationLogMap } from '@fastgpt/service/support/operationLog/constants';
|
||||
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
import UserBox from '@fastgpt/web/components/common/UserBox';
|
||||
@@ -52,7 +52,7 @@ function OperationLogTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const eventOptions = useMemo(
|
||||
() =>
|
||||
Object.values(OperationLogEventEnum).map((event) => ({
|
||||
label: t(operationLogI18nMap[event].typeLabel),
|
||||
label: t(operationLogMap[event].typeLabel),
|
||||
value: event
|
||||
})),
|
||||
[t]
|
||||
@@ -158,7 +158,7 @@ function OperationLogTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{operationLogs?.map((log) => {
|
||||
const i18nData = operationLogI18nMap[log.event];
|
||||
const i18nData = operationLogMap[log.event];
|
||||
const metadata = { ...log.metadata };
|
||||
|
||||
if (log.event === OperationLogEventEnum.ASSIGN_PERMISSION) {
|
||||
|
||||
@@ -124,7 +124,7 @@ const EditForm = ({
|
||||
}
|
||||
}));
|
||||
}
|
||||
}, [selectedModel]);
|
||||
}, [selectedModel, setAppForm]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,34 +1,305 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, HStack, StackProps } from '@chakra-ui/react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
|
||||
import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comment';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { Node, useReactFlow } from 'reactflow';
|
||||
import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext';
|
||||
import { WorkflowEventContext } from '../../context/workflowEventContext';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import dagre from '@dagrejs/dagre';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { WorkflowStatusContext } from '../../context/workflowStatusContext';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
|
||||
const ContextMenu = () => {
|
||||
const { t } = useTranslation();
|
||||
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes);
|
||||
const menu = useContextSelector(WorkflowEventContext, (v) => v.menu);
|
||||
const menu = useContextSelector(WorkflowEventContext, (v) => v.menu!);
|
||||
const setMenu = useContextSelector(WorkflowEventContext, (ctx) => ctx.setMenu);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const setEdges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setEdges);
|
||||
const getParentNodeSizeAndPosition = useContextSelector(
|
||||
WorkflowStatusContext,
|
||||
(v) => v.getParentNodeSizeAndPosition
|
||||
);
|
||||
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const newNode = nodeTemplate2FlowNode({
|
||||
template: CommentNode,
|
||||
position: screenToFlowPosition({ x: menu?.left ?? 0, y: menu?.top ?? 0 }),
|
||||
t
|
||||
});
|
||||
const { fitView, screenToFlowPosition } = useReactFlow();
|
||||
|
||||
const allUnFolded = useMemo(() => {
|
||||
return !!menu ? nodeList.some((node) => node.isFolded) : false;
|
||||
}, [nodeList, menu]);
|
||||
|
||||
return !!menu ? (
|
||||
const onLayout = useCallback(() => {
|
||||
const updateChildNodesPosition = ({
|
||||
startNode,
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
startNode: Node<FlowNodeItemType>;
|
||||
nodes: Node<FlowNodeItemType>[];
|
||||
edges: any[];
|
||||
}) => {
|
||||
const startPosition = { x: startNode.position.x, y: startNode.position.y };
|
||||
|
||||
const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
dagreGraph.setGraph({
|
||||
rankdir: 'LR',
|
||||
nodesep: 80, // Horizontal space
|
||||
ranksep: 120 // Vertical space
|
||||
});
|
||||
|
||||
nodes.forEach((node) => {
|
||||
dagreGraph.setNode(node.id, { width: node.width!, height: node.height! });
|
||||
});
|
||||
|
||||
// Find connected nodes
|
||||
const connectedNodeIds = new Set<string>();
|
||||
edges.forEach((edge) => {
|
||||
connectedNodeIds.add(edge.source);
|
||||
connectedNodeIds.add(edge.target);
|
||||
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
const layoutedStartNode = dagreGraph.node(startNode.data.nodeId);
|
||||
const offsetX = startPosition.x - (layoutedStartNode.x - startNode.width! / 2);
|
||||
const offsetY = startPosition.y - (layoutedStartNode.y - startNode.height! / 2);
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (!connectedNodeIds.has(node.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
|
||||
node.position = {
|
||||
x: nodeWithPosition.x - node.width! / 2 + offsetX,
|
||||
y: nodeWithPosition.y - node.height! / 2 + offsetY
|
||||
};
|
||||
});
|
||||
};
|
||||
const updateParentNodesPosition = ({
|
||||
startNode,
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
startNode: Node<FlowNodeItemType>;
|
||||
nodes: Node<FlowNodeItemType>[];
|
||||
edges: any[];
|
||||
}) => {
|
||||
const startPosition = { x: startNode.position.x, y: startNode.position.y };
|
||||
|
||||
const childNodeIdsSet = new Set(
|
||||
nodes.filter((node) => !!node.data.parentNodeId).map((node) => node.data.nodeId)
|
||||
);
|
||||
|
||||
const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
dagreGraph.setGraph({
|
||||
rankdir: 'LR',
|
||||
nodesep: 80, // Horizontal space
|
||||
ranksep: 120 // Vertical space
|
||||
});
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (childNodeIdsSet.has(node.data.nodeId)) return;
|
||||
dagreGraph.setNode(node.id, { width: node.width!, height: node.height! });
|
||||
});
|
||||
|
||||
// Find connected nodes
|
||||
const connectedNodeIds = new Set<string>();
|
||||
edges.forEach((edge) => {
|
||||
if (childNodeIdsSet.has(edge.source)) return;
|
||||
if (childNodeIdsSet.has(edge.target)) return;
|
||||
|
||||
connectedNodeIds.add(edge.source);
|
||||
connectedNodeIds.add(edge.target);
|
||||
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
const layoutedStartNode = dagreGraph.node(startNode.data.nodeId);
|
||||
const offsetX = startPosition.x - (layoutedStartNode.x - startNode.width! / 2);
|
||||
const offsetY = startPosition.y - (layoutedStartNode.y - startNode.height! / 2);
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (!connectedNodeIds.has(node.id) || childNodeIdsSet.has(node.data.nodeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
const targetX = nodeWithPosition.x - node.width! / 2 + offsetX;
|
||||
const targetY = nodeWithPosition.y - node.height! / 2 + offsetY;
|
||||
const diffX = targetX - node.position.x;
|
||||
const diffY = targetY - node.position.y;
|
||||
node.position = {
|
||||
x: targetX,
|
||||
y: targetY
|
||||
};
|
||||
|
||||
// Update child nodes position
|
||||
nodes.forEach((childNode) => {
|
||||
if (childNode.data.parentNodeId === node.data.nodeId) {
|
||||
childNode.position = {
|
||||
x: childNode.position.x + diffX,
|
||||
y: childNode.position.y + diffY
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
setNodes((nodes) => {
|
||||
let newNodes = cloneDeep(nodes);
|
||||
|
||||
setEdges((edges) => {
|
||||
const childNodesIdSet = new Set();
|
||||
|
||||
// 1. Layout child nodes
|
||||
const childNodesMap: Record<string, Node<FlowNodeItemType>[]> = {};
|
||||
newNodes.forEach((node) => {
|
||||
const parentId = node.data.parentNodeId;
|
||||
if (parentId) {
|
||||
childNodesIdSet.add(parentId);
|
||||
if (!childNodesMap[parentId]) {
|
||||
childNodesMap[parentId] = [];
|
||||
}
|
||||
childNodesMap[parentId].push(node);
|
||||
}
|
||||
});
|
||||
const childNodesArr = Object.values(childNodesMap);
|
||||
if (childNodesArr.length > 0) {
|
||||
childNodesArr.forEach((childNodes) => {
|
||||
updateChildNodesPosition({
|
||||
startNode: childNodes[0],
|
||||
nodes: childNodes,
|
||||
edges
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Reset parent node size and position
|
||||
const parentNodes = newNodes.filter((node) => childNodesIdSet.has(node.data.nodeId));
|
||||
parentNodes.forEach((node) => {
|
||||
const res = getParentNodeSizeAndPosition({
|
||||
nodes: newNodes,
|
||||
parentId: node.data.nodeId
|
||||
});
|
||||
if (!res) return;
|
||||
const { parentX, parentY, nodeWidth, nodeHeight, childHeight, childWidth } = res;
|
||||
|
||||
node.position = {
|
||||
x: parentX,
|
||||
y: parentY
|
||||
};
|
||||
node.width = nodeWidth;
|
||||
node.height = nodeHeight;
|
||||
node.data.inputs.forEach((input) => {
|
||||
if (input.key === NodeInputKeyEnum.nodeHeight) {
|
||||
input.value = childHeight;
|
||||
} else if (input.key === NodeInputKeyEnum.nodeWidth) {
|
||||
input.value = childWidth;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 3. Layout parent node
|
||||
updateParentNodesPosition({
|
||||
startNode:
|
||||
newNodes.find((node) =>
|
||||
[
|
||||
FlowNodeTypeEnum.systemConfig,
|
||||
FlowNodeTypeEnum.pluginConfig,
|
||||
FlowNodeTypeEnum.workflowStart,
|
||||
FlowNodeTypeEnum.pluginInput
|
||||
].includes(node.data.flowNodeType)
|
||||
) || newNodes[0],
|
||||
nodes: newNodes,
|
||||
edges
|
||||
});
|
||||
return edges;
|
||||
});
|
||||
|
||||
return newNodes;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
fitView();
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onAddComment = useCallback(() => {
|
||||
const newNode = nodeTemplate2FlowNode({
|
||||
template: CommentNode,
|
||||
position: screenToFlowPosition({ x: menu?.left ?? 0, y: (menu?.top ?? 0) + 100 }),
|
||||
t
|
||||
});
|
||||
|
||||
setNodes((state) => {
|
||||
const newState = state
|
||||
.map((node) => ({
|
||||
...node,
|
||||
selected: false
|
||||
}))
|
||||
// @ts-ignore
|
||||
.concat(newNode);
|
||||
return newState;
|
||||
});
|
||||
}, [menu]);
|
||||
|
||||
const onFold = useCallback(() => {
|
||||
setNodes((state) => {
|
||||
return state.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isFolded: !allUnFolded
|
||||
}
|
||||
}));
|
||||
});
|
||||
}, [allUnFolded]);
|
||||
|
||||
const ContextMenuItem = useCallback(
|
||||
({
|
||||
icon,
|
||||
label,
|
||||
onClick,
|
||||
...props
|
||||
}: {
|
||||
icon: string;
|
||||
label: string;
|
||||
onClick: () => any;
|
||||
} & StackProps) => {
|
||||
return (
|
||||
<HStack
|
||||
px={2}
|
||||
py={1}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'sm'}
|
||||
_hover={{ bg: 'myGray.50', color: 'primary.500' }}
|
||||
onClick={() => {
|
||||
onClick();
|
||||
setMenu(null);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<MyIcon name={icon as any} w={'1rem'} ml={1} />
|
||||
<Box fontSize={'sm'} fontWeight={'500'}>
|
||||
{label}
|
||||
</Box>
|
||||
</HStack>
|
||||
);
|
||||
},
|
||||
[setMenu]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position="absolute"
|
||||
@@ -55,61 +326,26 @@ const ContextMenu = () => {
|
||||
p={1}
|
||||
zIndex={10}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={2}
|
||||
py={1}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'sm'}
|
||||
_hover={{ bg: 'myGray.50', color: 'primary.500' }}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
setNodes((state) => {
|
||||
const newState = state
|
||||
.map((node) => ({
|
||||
...node,
|
||||
selected: false
|
||||
}))
|
||||
// @ts-ignore
|
||||
.concat(newNode);
|
||||
return newState;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name="comment" w={'1rem'} ml={1} />
|
||||
<Box fontSize={'12px'} fontWeight={'500'} ml={1.5}>
|
||||
{t('workflow:context_menu.add_comment')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex
|
||||
mt={1}
|
||||
alignItems={'center'}
|
||||
px={2}
|
||||
py={1}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'sm'}
|
||||
_hover={{ bg: 'myGray.50', color: 'primary.500' }}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
setNodes((state) => {
|
||||
return state.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isFolded: !allUnFolded
|
||||
}
|
||||
}));
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name="common/select" w={'1rem'} ml={1} />
|
||||
<Box fontSize={'12px'} fontWeight={'500'} ml={1.5}>
|
||||
{allUnFolded ? t('workflow:unFoldAll') : t('workflow:foldAll')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<ContextMenuItem
|
||||
mb={1}
|
||||
icon="alignLeft"
|
||||
label={t('workflow:auto_align')}
|
||||
onClick={onLayout}
|
||||
/>
|
||||
<ContextMenuItem
|
||||
mb={1}
|
||||
icon="comment"
|
||||
label={t('workflow:context_menu.add_comment')}
|
||||
onClick={onAddComment}
|
||||
/>
|
||||
<ContextMenuItem
|
||||
icon="common/select"
|
||||
label={allUnFolded ? t('workflow:unFoldAll') : t('workflow:foldAll')}
|
||||
onClick={onFold}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null;
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ContextMenu);
|
||||
|
||||
@@ -73,6 +73,7 @@ const Workflow = () => {
|
||||
WorkflowEventContext,
|
||||
(v) => v.workflowControlMode
|
||||
);
|
||||
const menu = useContextSelector(WorkflowEventContext, (v) => v.menu);
|
||||
|
||||
const {
|
||||
handleNodesChange,
|
||||
@@ -163,7 +164,7 @@ const Workflow = () => {
|
||||
: {})}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
>
|
||||
<ContextMenu />
|
||||
{!!menu && <ContextMenu />}
|
||||
<FlowController />
|
||||
<HelperLines horizontal={helperLineHorizontal} vertical={helperLineVertical} />
|
||||
</ReactFlow>
|
||||
|
||||
@@ -26,7 +26,7 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5,
|
||||
usingReRank: false
|
||||
usingReRank: true
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -27,7 +27,7 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
|
||||
embeddingWeight: 0.5,
|
||||
limit: 3000,
|
||||
similarity: 0.5,
|
||||
usingReRank: false,
|
||||
usingReRank: true,
|
||||
rerankModel: defaultModels.llm?.model,
|
||||
rerankWeight: 0.6,
|
||||
datasetSearchUsingExtensionQuery: true,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AppContext } from '../../context';
|
||||
import { compareSnapshot } from '@/web/core/workflow/utils';
|
||||
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getNodesBounds, Node } from 'reactflow';
|
||||
import { Node } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
@@ -20,6 +20,22 @@ type WorkflowStatusContextType = {
|
||||
isSaved: boolean;
|
||||
leaveSaveSign: React.MutableRefObject<boolean>;
|
||||
resetParentNodeSizeAndPosition: (parentId: string) => void;
|
||||
getParentNodeSizeAndPosition: ({
|
||||
nodes,
|
||||
parentId
|
||||
}: {
|
||||
nodes: Node<FlowNodeItemType>[];
|
||||
parentId: string;
|
||||
}) =>
|
||||
| {
|
||||
parentX: number;
|
||||
parentY: number;
|
||||
childWidth: number;
|
||||
childHeight: number;
|
||||
nodeWidth: number;
|
||||
nodeHeight: number;
|
||||
}
|
||||
| undefined;
|
||||
};
|
||||
|
||||
export const WorkflowStatusContext = createContext<WorkflowStatusContextType>({
|
||||
@@ -94,30 +110,70 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
|
||||
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const getParentNodeSizeAndPosition = useMemoizedFn(
|
||||
({ nodes, parentId }: { nodes: Node<FlowNodeItemType>[]; parentId: string }) => {
|
||||
const { childNodes, loopNode } = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.data.parentNodeId === parentId) {
|
||||
acc.childNodes.push(node);
|
||||
}
|
||||
if (node.id === parentId) {
|
||||
acc.loopNode = node;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
|
||||
);
|
||||
|
||||
if (!loopNode) return;
|
||||
const loopChilWidth =
|
||||
loopNode.data.inputs.find((node) => node.key === NodeInputKeyEnum.nodeWidth)?.value ?? 0;
|
||||
const loopChilHeight =
|
||||
loopNode.data.inputs.find((node) => node.key === NodeInputKeyEnum.nodeHeight)?.value ?? 0;
|
||||
|
||||
// 初始化为第一个节点的边界
|
||||
let minX = childNodes[0].position.x;
|
||||
let minY = childNodes[0].position.y;
|
||||
let maxX = childNodes[0].position.x + (childNodes[0].width || 0);
|
||||
let maxY = childNodes[0].position.y + (childNodes[0].height || 0);
|
||||
|
||||
// 遍历所有节点找出最小/最大边界
|
||||
childNodes.forEach((node) => {
|
||||
const nodeWidth = node.width || 0;
|
||||
const nodeHeight = node.height || 0;
|
||||
|
||||
minX = Math.min(minX, node.position.x);
|
||||
minY = Math.min(minY, node.position.y);
|
||||
maxX = Math.max(maxX, node.position.x + nodeWidth);
|
||||
maxY = Math.max(maxY, node.position.y + nodeHeight);
|
||||
});
|
||||
|
||||
const childWidth = Math.max(maxX - minX + 80, 840);
|
||||
const childHeight = Math.max(maxY - minY + 80, 600);
|
||||
|
||||
const diffWidth = childWidth - loopChilWidth;
|
||||
const diffHeight = childHeight - loopChilHeight;
|
||||
const targetNodeWidth = (loopNode.width ?? 0) + diffWidth;
|
||||
const targetNodeHeight = (loopNode.height ?? 0) + diffHeight;
|
||||
|
||||
const offsetHeight =
|
||||
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
|
||||
?.value ?? 83;
|
||||
|
||||
return {
|
||||
parentX: Math.round(minX - 70),
|
||||
parentY: Math.round(minY - offsetHeight - 240),
|
||||
childWidth,
|
||||
childHeight,
|
||||
nodeWidth: targetNodeWidth,
|
||||
nodeHeight: targetNodeHeight
|
||||
};
|
||||
}
|
||||
);
|
||||
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
|
||||
const { childNodes, loopNode } = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.data.parentNodeId === parentId) {
|
||||
acc.childNodes.push(node);
|
||||
}
|
||||
if (node.id === parentId) {
|
||||
acc.loopNode = node;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
|
||||
);
|
||||
|
||||
if (!loopNode) return;
|
||||
|
||||
const rect = getNodesBounds(childNodes);
|
||||
// Calculate parent node size with minimum width/height constraints
|
||||
const width = Math.max(rect.width + 80, 840);
|
||||
const height = Math.max(rect.height + 80, 600);
|
||||
|
||||
const offsetHeight =
|
||||
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
|
||||
?.value ?? 83;
|
||||
const res = getParentNodeSizeAndPosition({ nodes, parentId });
|
||||
if (!res) return;
|
||||
const { parentX, parentY, childWidth, childHeight } = res;
|
||||
|
||||
// Update parentNode size and position
|
||||
onChangeNode({
|
||||
@@ -126,7 +182,7 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
key: NodeInputKeyEnum.nodeWidth,
|
||||
value: {
|
||||
...Input_Template_Node_Width,
|
||||
value: width
|
||||
value: childWidth
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
@@ -135,7 +191,7 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
key: NodeInputKeyEnum.nodeHeight,
|
||||
value: {
|
||||
...Input_Template_Node_Height,
|
||||
value: height
|
||||
value: childHeight
|
||||
}
|
||||
});
|
||||
// Update parentNode position
|
||||
@@ -144,8 +200,8 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
id: parentId,
|
||||
type: 'position',
|
||||
position: {
|
||||
x: Math.round(rect.x - 70),
|
||||
y: Math.round(rect.y - offsetHeight - 240)
|
||||
x: parentX,
|
||||
y: parentY
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -155,9 +211,10 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
return {
|
||||
isSaved,
|
||||
leaveSaveSign,
|
||||
resetParentNodeSizeAndPosition
|
||||
resetParentNodeSizeAndPosition,
|
||||
getParentNodeSizeAndPosition
|
||||
};
|
||||
}, [isSaved, resetParentNodeSizeAndPosition]);
|
||||
}, [isSaved, resetParentNodeSizeAndPosition, getParentNodeSizeAndPosition]);
|
||||
return (
|
||||
<WorkflowStatusContext.Provider value={contextValue}>{children}</WorkflowStatusContext.Provider>
|
||||
);
|
||||
|
||||
@@ -35,6 +35,7 @@ import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||
import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { postCreateMcpServer, putUpdateMcpServer } from '../../../web/support/mcp/api';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
export type EditMcForm = {
|
||||
id?: string;
|
||||
@@ -62,7 +63,7 @@ const SelectAppModal = ({
|
||||
{
|
||||
appId: string;
|
||||
toolName: string;
|
||||
toolAlias: string;
|
||||
appName: string;
|
||||
avatar: string;
|
||||
description: string;
|
||||
}[]
|
||||
@@ -76,7 +77,7 @@ const SelectAppModal = ({
|
||||
data.map((item) => ({
|
||||
appId: item.id,
|
||||
toolName: item.name,
|
||||
toolAlias: item.name,
|
||||
appName: item.name,
|
||||
avatar: item.avatar,
|
||||
description: selectedApps.find((app) => app.appId === item.id)?.description || ''
|
||||
}))
|
||||
@@ -176,7 +177,7 @@ const SelectAppModal = ({
|
||||
{
|
||||
appId: item._id,
|
||||
toolName: item.name,
|
||||
toolAlias: item.name,
|
||||
appName: item.name,
|
||||
avatar: item.avatar,
|
||||
description: item.intro
|
||||
}
|
||||
@@ -281,7 +282,7 @@ const EditMcpModal = ({
|
||||
apps: data.apps.map((item) => ({
|
||||
appId: item.appId,
|
||||
toolName: item.toolName,
|
||||
toolAlias: item.toolAlias,
|
||||
appName: item.appName,
|
||||
description: item.description
|
||||
}))
|
||||
}),
|
||||
@@ -299,7 +300,7 @@ const EditMcpModal = ({
|
||||
apps: data.apps.map((item) => ({
|
||||
appId: item.appId,
|
||||
toolName: item.toolName,
|
||||
toolAlias: item.toolAlias,
|
||||
appName: item.appName,
|
||||
description: item.description
|
||||
}))
|
||||
}),
|
||||
@@ -317,7 +318,7 @@ const EditMcpModal = ({
|
||||
iconSrc="key"
|
||||
title={isEdit ? t('dashboard_mcp:edit_mcp') : t('dashboard_mcp:create_mcp')}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '600px']}
|
||||
maxW={['90vw', '800px']}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
>
|
||||
@@ -339,8 +340,11 @@ const EditMcpModal = ({
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>
|
||||
{t('dashboard_mcp:tool_name')}
|
||||
<QuestionTip label={t('dashboard_mcp:tool_name_tip')} />
|
||||
</Th>
|
||||
<Th>{t('dashboard_mcp:app_name')}</Th>
|
||||
<Th>{t('dashboard_mcp:app_tool_name')}</Th>
|
||||
<Th>{t('dashboard_mcp:app_description')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
@@ -349,15 +353,15 @@ const EditMcpModal = ({
|
||||
{apps.map((app, index) => {
|
||||
return (
|
||||
<Tr key={app.id} fontWeight={500} fontSize={'mini'} color={'myGray.900'}>
|
||||
<Td>{app.toolName}</Td>
|
||||
<Td>
|
||||
<Input
|
||||
{...register(`apps.${index}.toolAlias`)}
|
||||
placeholder={app.toolName}
|
||||
{...register(`apps.${index}.toolName`, { required: true })}
|
||||
placeholder={t('dashboard_mcp:tool_name_placeholder')}
|
||||
bg={'myGray.50'}
|
||||
w={'100%'}
|
||||
/>
|
||||
</Td>
|
||||
<Td>{app.appName}</Td>
|
||||
<Td>
|
||||
<Input
|
||||
{...register(`apps.${index}.description`, { required: true })}
|
||||
@@ -413,7 +417,7 @@ const EditMcpModal = ({
|
||||
e.map((item) => ({
|
||||
appId: item.appId,
|
||||
toolName: item.toolName,
|
||||
toolAlias: item.toolAlias,
|
||||
appName: item.appName,
|
||||
description: item.description
|
||||
}))
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
searchParams: {
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
embeddingWeight: 0.5,
|
||||
usingReRank: false,
|
||||
usingReRank: true,
|
||||
rerankModel: defaultModels?.rerank?.model,
|
||||
rerankWeight: 0.5,
|
||||
limit: 5000,
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import { Box, Flex, Grid, Button, VStack } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
|
||||
import { postCreatePayBill } from '@/web/support/wallet/bill/api';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { subPlans } = useSystemStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
|
||||
// extra dataset
|
||||
@@ -26,40 +25,33 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
month: 1
|
||||
}
|
||||
});
|
||||
const onclickBuyDatasetSize = useCallback(
|
||||
const { runAsync: onclickBuyDatasetSize, loading: isLoadingBuyDatasetSize } = useRequest2(
|
||||
async ({ datasetSize, month }: { datasetSize: number; month: number }) => {
|
||||
try {
|
||||
datasetSize = Math.ceil(datasetSize);
|
||||
month = Math.ceil(month);
|
||||
datasetSize = Math.ceil(datasetSize);
|
||||
month = Math.ceil(month);
|
||||
|
||||
const datasetSizePayAmount = datasetSize * month * extraDatasetPrice;
|
||||
if (datasetSizePayAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.wallet.amount_0')
|
||||
});
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.extraDatasetSub,
|
||||
month,
|
||||
extraDatasetSize: datasetSize
|
||||
});
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
status: 'error'
|
||||
const datasetSizePayAmount = datasetSize * month * extraDatasetPrice;
|
||||
if (datasetSizePayAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.wallet.amount_0')
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
const res = await postCreatePayBill({
|
||||
type: BillTypeEnum.extraDatasetSub,
|
||||
month,
|
||||
extraDatasetSize: datasetSize
|
||||
});
|
||||
setQRPayData({
|
||||
tip: t('common:button.extra_dataset_size_tip'),
|
||||
...res
|
||||
});
|
||||
},
|
||||
[extraDatasetPrice, toast]
|
||||
{
|
||||
manual: true,
|
||||
refreshDeps: [extraDatasetPrice]
|
||||
}
|
||||
);
|
||||
|
||||
// extra ai points
|
||||
@@ -70,41 +62,34 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
month: 1
|
||||
}
|
||||
});
|
||||
const onclickBuyExtraPoints = useCallback(
|
||||
const { runAsync: onclickBuyExtraPoints, loading: isLoadingBuyExtraPoints } = useRequest2(
|
||||
async ({ points }: { points: number }) => {
|
||||
try {
|
||||
points = Math.ceil(points);
|
||||
points = Math.ceil(points);
|
||||
|
||||
const month = 1;
|
||||
const payAmount = points * month * extraPointsPrice;
|
||||
const month = 1;
|
||||
const payAmount = points * month * extraPointsPrice;
|
||||
|
||||
if (payAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.wallet.amount_0')
|
||||
});
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.extraPoints,
|
||||
extraPoints: points
|
||||
});
|
||||
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
status: 'error'
|
||||
if (payAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.wallet.amount_0')
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
const res = await postCreatePayBill({
|
||||
type: BillTypeEnum.extraPoints,
|
||||
extraPoints: points
|
||||
});
|
||||
|
||||
setQRPayData({
|
||||
tip: t('common:button.extra_points_tip'),
|
||||
...res
|
||||
});
|
||||
},
|
||||
[extraPointsPrice, toast]
|
||||
{
|
||||
manual: true,
|
||||
refreshDeps: [extraPointsPrice]
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -184,7 +169,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={loading}
|
||||
isLoading={isLoadingBuyDatasetSize}
|
||||
onClick={handleSubmitDatasetSize(onclickBuyDatasetSize)}
|
||||
color={'primary.700'}
|
||||
>
|
||||
@@ -264,7 +249,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={loading}
|
||||
isLoading={isLoadingBuyExtraPoints}
|
||||
onClick={handleSubmitExtraPoints(onclickBuyExtraPoints)}
|
||||
color={'primary.700'}
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constant
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
|
||||
import { postCreatePayBill } from '@/web/support/wallet/bill/api';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||
|
||||
@@ -53,9 +53,9 @@ const Standard = ({
|
||||
permissionCustomApiKey: value.permissionCustomApiKey,
|
||||
permissionCustomCopyright: value.permissionCustomCopyright,
|
||||
trainingWeight: value.trainingWeight,
|
||||
permissionReRank: value.permissionReRank,
|
||||
totalPoints: value.totalPoints * (selectSubMode === SubModeEnum.month ? 1 : 12),
|
||||
permissionWebsiteSync: value.permissionWebsiteSync
|
||||
permissionWebsiteSync: value.permissionWebsiteSync,
|
||||
permissionTeamOperationLog: value.permissionTeamOperationLog
|
||||
};
|
||||
})
|
||||
: [];
|
||||
@@ -65,13 +65,9 @@ const Standard = ({
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
|
||||
/* Get pay code */
|
||||
const { runAsync: onPay, loading: isLoading } = useRequest2(getWxPayQRCode, {
|
||||
const { runAsync: onPay, loading: isLoading } = useRequest2(postCreatePayBill, {
|
||||
onSuccess(res) {
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
setQRPayData(res);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { putUpdateMemberName } from '@/web/support/user/team/api';
|
||||
import { putUpdateMemberName, redeemCoupon } from '@/web/support/user/team/api';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import {
|
||||
StandardSubLevelEnum,
|
||||
@@ -47,6 +47,9 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useMount } from 'ahooks';
|
||||
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||
|
||||
const RedeemCouponModal = dynamic(() => import('@/pageComponents/account/info/RedeemCouponModal'), {
|
||||
ssr: false
|
||||
});
|
||||
const StandDetailModal = dynamic(
|
||||
() => import('@/pageComponents/account/info/standardDetailModal'),
|
||||
{ ssr: false }
|
||||
@@ -353,8 +356,8 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => {
|
||||
const PlanUsage = () => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, initUserInfo, teamPlanStatus } = useUserStore();
|
||||
const { subPlans } = useSystemStore();
|
||||
const { userInfo, initUserInfo, teamPlanStatus, initTeamPlanStatus } = useUserStore();
|
||||
const { subPlans, feConfigs } = useSystemStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
@@ -365,6 +368,12 @@ const PlanUsage = () => {
|
||||
onOpen: onOpenStandardModal
|
||||
} = useDisclosure();
|
||||
|
||||
const {
|
||||
isOpen: isOpenRedeemCouponModal,
|
||||
onClose: onCloseRedeemCouponModal,
|
||||
onOpen: onOpenRedeemCouponModal
|
||||
} = useDisclosure();
|
||||
|
||||
const planName = useMemo(() => {
|
||||
if (!teamPlanStatus?.standard?.currentSubLevel) return '';
|
||||
return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label;
|
||||
@@ -460,14 +469,19 @@ const PlanUsage = () => {
|
||||
</Flex>
|
||||
<ModelPriceModal>
|
||||
{({ onOpen }) => (
|
||||
<Button ml={4} size={'sm'} onClick={onOpen}>
|
||||
<Button ml={3} size={'sm'} onClick={onOpen}>
|
||||
{t('account_info:billing_standard')}
|
||||
</Button>
|
||||
)}
|
||||
</ModelPriceModal>
|
||||
<Button ml={4} variant={'whitePrimary'} size={'sm'} onClick={onOpenStandardModal}>
|
||||
<Button ml={3} variant={'whitePrimary'} size={'sm'} onClick={onOpenStandardModal}>
|
||||
{t('account_info:package_details')}
|
||||
</Button>
|
||||
{userInfo?.permission.isOwner && feConfigs?.show_coupon && (
|
||||
<Button ml={3} variant={'whitePrimary'} size={'sm'} onClick={onOpenRedeemCouponModal}>
|
||||
{t('account_info:redeem_coupon')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Box
|
||||
mt={[3, 6]}
|
||||
@@ -603,6 +617,12 @@ const PlanUsage = () => {
|
||||
</Box>
|
||||
</Box>
|
||||
{isOpenStandardModal && <StandDetailModal onClose={onCloseStandardModal} />}
|
||||
{isOpenRedeemCouponModal && (
|
||||
<RedeemCouponModal
|
||||
onClose={onCloseRedeemCouponModal}
|
||||
onSuccess={() => initTeamPlanStatus()}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { TeamContext, TeamModalContextProvider } from '@/pageComponents/account/team/context';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
const MemberTable = dynamic(() => import('@/pageComponents/account/team/MemberTable'));
|
||||
const PermissionManage = dynamic(
|
||||
@@ -48,7 +50,19 @@ const Team = () => {
|
||||
const { teamTab = TeamTabEnum.member } = router.query as { teamTab: `${TeamTabEnum}` };
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
const { userInfo, teamPlanStatus } = useUserStore();
|
||||
const standardPlan = teamPlanStatus?.standard;
|
||||
const level = standardPlan?.currentSubLevel;
|
||||
const { subPlans } = useSystemStore();
|
||||
const planContent = useMemo(() => {
|
||||
const plan = level !== undefined ? subPlans?.standard?.[level] : undefined;
|
||||
|
||||
if (!plan) return;
|
||||
return {
|
||||
permissionTeamOperationLog: plan.permissionTeamOperationLog
|
||||
};
|
||||
}, [subPlans?.standard, level]);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { setEditTeamData, teamSize } = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
@@ -65,6 +79,13 @@ const Team = () => {
|
||||
px={'1rem'}
|
||||
value={teamTab}
|
||||
onChange={(e) => {
|
||||
if (e === TeamTabEnum.operationLog && !planContent?.permissionTeamOperationLog) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:not_permission')
|
||||
});
|
||||
return;
|
||||
}
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
|
||||
import { PgClient } from '@fastgpt/service/common/vectorDB/pg/controller';
|
||||
|
||||
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { MongoDatasetDataText } from '@fastgpt/service/core/dataset/data/dataTex
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type';
|
||||
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
|
||||
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorDB/controller';
|
||||
|
||||
// 删了库,没删集合
|
||||
const checkInvalidCollection = async () => {
|
||||
|
||||
@@ -5,8 +5,8 @@ import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection
|
||||
import { DatasetCollectionDataProcessModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants';
|
||||
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
|
||||
import { PG_ADDRESS } from '@fastgpt/service/common/vectorStore/constants';
|
||||
import { PgClient } from '@fastgpt/service/common/vectorDB/pg/controller';
|
||||
import { PG_ADDRESS } from '@fastgpt/service/common/vectorDB/constants';
|
||||
|
||||
// 所有 trainingType=auto 的 collection,都改成 trainingType=chunk
|
||||
const updateCollections = async () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { MilvusCtrl } from '@fastgpt/service/common/vectorStore/milvus/class';
|
||||
import { DatasetVectorTableName } from '@fastgpt/service/common/vectorStore/constants';
|
||||
import { MilvusCtrl } from '@fastgpt/service/common/vectorDB/milvus/index';
|
||||
import { DatasetVectorTableName } from '@fastgpt/service/common/vectorDB/constants';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
|
||||
@@ -63,14 +63,6 @@ export type Props = {
|
||||
};
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res.on('close', () => {
|
||||
res.end();
|
||||
});
|
||||
res.on('error', () => {
|
||||
console.log('error: ', 'request error');
|
||||
res.end();
|
||||
});
|
||||
|
||||
let {
|
||||
nodes = [],
|
||||
edges = [],
|
||||
@@ -170,39 +162,40 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
});
|
||||
|
||||
/* start process */
|
||||
const { flowResponses, assistantResponses, newVariables, flowUsages } = await dispatchWorkFlow({
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'test',
|
||||
timezone,
|
||||
externalProvider,
|
||||
uid: tmbId,
|
||||
const { flowResponses, assistantResponses, newVariables, flowUsages, durationSeconds } =
|
||||
await dispatchWorkFlow({
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'test',
|
||||
timezone,
|
||||
externalProvider,
|
||||
uid: tmbId,
|
||||
|
||||
runningAppInfo: {
|
||||
id: appId,
|
||||
teamId: app.teamId,
|
||||
tmbId: app.tmbId
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
runningAppInfo: {
|
||||
id: appId,
|
||||
teamId: app.teamId,
|
||||
tmbId: app.tmbId
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes,
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges, interactive),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
lastInteractive: interactive,
|
||||
chatConfig,
|
||||
histories: newHistories,
|
||||
stream: true,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite,
|
||||
version: 'v2',
|
||||
responseDetail: true
|
||||
});
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes,
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges, interactive),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
lastInteractive: interactive,
|
||||
chatConfig,
|
||||
histories: newHistories,
|
||||
stream: true,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite,
|
||||
version: 'v2',
|
||||
responseDetail: true
|
||||
});
|
||||
|
||||
workflowResponseWrite({
|
||||
event: SseResponseEventEnum.answer,
|
||||
@@ -238,7 +231,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
appId: app._id,
|
||||
userInteractiveVal,
|
||||
aiResponse,
|
||||
newVariables
|
||||
newVariables,
|
||||
durationSeconds
|
||||
});
|
||||
} else {
|
||||
await saveChat({
|
||||
@@ -252,7 +246,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
isUpdateUseTime: false, // owner update use time
|
||||
newTitle,
|
||||
source: ChatSourceEnum.test,
|
||||
content: [userQuestion, aiResponse]
|
||||
content: [userQuestion, aiResponse],
|
||||
durationSeconds
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -53,11 +53,11 @@ async function handler(
|
||||
const isOutLink = authType === GetChatTypeEnum.outLink;
|
||||
|
||||
const fieldMap = {
|
||||
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time hideInUI ${
|
||||
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time hideInUI durationSeconds ${
|
||||
DispatchNodeResponseKeyEnum.nodeResponse
|
||||
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`,
|
||||
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI ${DispatchNodeResponseKeyEnum.nodeResponse}`,
|
||||
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI durationSeconds ${DispatchNodeResponseKeyEnum.nodeResponse}`,
|
||||
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI durationSeconds ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||
};
|
||||
|
||||
const { total, histories } = await getChatItems({
|
||||
|
||||
@@ -11,9 +11,8 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { DatasetCollectionItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { collectionTagsToTagLabel } from '@fastgpt/service/core/dataset/collection/utils';
|
||||
import { getVectorCountByCollectionId } from '@fastgpt/service/common/vectorStore/controller';
|
||||
import { getVectorCountByCollectionId } from '@fastgpt/service/common/vectorDB/controller';
|
||||
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { Types } from 'mongoose';
|
||||
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
|
||||
|
||||
async function handler(req: NextApiRequest): Promise<DatasetCollectionItemType> {
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import type { SearchTestProps, SearchTestResponse } from '@/global/core/dataset/api.d';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { pushGenerateVectorUsage, pushRerankUsage } from '@/service/support/wallet/usage/push';
|
||||
import {
|
||||
deepRagSearch,
|
||||
defaultSearchDatasetData
|
||||
} from '@fastgpt/service/core/dataset/search/controller';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import {
|
||||
checkTeamAIPoints,
|
||||
checkTeamReRankPermission
|
||||
} from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { checkTeamAIPoints } from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
@@ -58,6 +55,8 @@ async function handler(req: ApiRequestProps<SearchTestProps>): Promise<SearchTes
|
||||
// auth balance
|
||||
await checkTeamAIPoints(teamId);
|
||||
|
||||
const rerankModelData = getRerankModel(rerankModel);
|
||||
|
||||
const searchData = {
|
||||
histories: [],
|
||||
teamId,
|
||||
@@ -69,11 +68,19 @@ async function handler(req: ApiRequestProps<SearchTestProps>): Promise<SearchTes
|
||||
datasetIds: [datasetId],
|
||||
searchMode,
|
||||
embeddingWeight,
|
||||
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId)),
|
||||
rerankModel: getRerankModel(rerankModel),
|
||||
usingReRank,
|
||||
rerankModel: rerankModelData,
|
||||
rerankWeight
|
||||
};
|
||||
const { searchRes, tokens, queryExtensionResult, deepSearchResult, ...result } = datasetDeepSearch
|
||||
const {
|
||||
searchRes,
|
||||
embeddingTokens,
|
||||
reRankInputTokens,
|
||||
usingReRank: searchUsingReRank,
|
||||
queryExtensionResult,
|
||||
deepSearchResult,
|
||||
...result
|
||||
} = datasetDeepSearch
|
||||
? await deepRagSearch({
|
||||
...searchData,
|
||||
datasetDeepSearchModel,
|
||||
@@ -88,10 +95,10 @@ async function handler(req: ApiRequestProps<SearchTestProps>): Promise<SearchTes
|
||||
});
|
||||
|
||||
// push bill
|
||||
const { totalPoints } = pushGenerateVectorUsage({
|
||||
const { totalPoints: embeddingTotalPoints } = pushGenerateVectorUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
inputTokens: tokens,
|
||||
inputTokens: reRankInputTokens,
|
||||
model: dataset.vectorModel,
|
||||
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
|
||||
|
||||
@@ -106,10 +113,19 @@ async function handler(req: ApiRequestProps<SearchTestProps>): Promise<SearchTes
|
||||
deepSearchOutputTokens: deepSearchResult.outputTokens
|
||||
})
|
||||
});
|
||||
const { totalPoints: reRankTotalPoints } = searchUsingReRank
|
||||
? pushRerankUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
inputTokens: reRankInputTokens,
|
||||
model: rerankModelData.model
|
||||
})
|
||||
: { totalPoints: 0 };
|
||||
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
apikey,
|
||||
totalPoints: totalPoints
|
||||
totalPoints: embeddingTotalPoints + reRankTotalPoints
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,6 +133,7 @@ async function handler(req: ApiRequestProps<SearchTestProps>): Promise<SearchTes
|
||||
list: searchRes,
|
||||
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
|
||||
queryExtensionModel: queryExtensionResult?.model,
|
||||
usingReRank: searchUsingReRank,
|
||||
...result
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ToolType } from '@fastgpt/global/core/app/type';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import getMCPClient from '@fastgpt/service/core/app/mcp';
|
||||
import { MCPClient } from '@fastgpt/service/core/app/mcp';
|
||||
|
||||
export type getMCPToolsQuery = {};
|
||||
|
||||
@@ -15,14 +15,9 @@ async function handler(
|
||||
): Promise<getMCPToolsResponse> {
|
||||
const { url } = req.body;
|
||||
|
||||
const mcpClient = getMCPClient({ url });
|
||||
const mcpClient = new MCPClient({ url });
|
||||
|
||||
try {
|
||||
const tools = await mcpClient.getTools();
|
||||
return tools;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return mcpClient.getTools();
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import getMCPClient from '@fastgpt/service/core/app/mcp';
|
||||
import { MCPClient } from '@fastgpt/service/core/app/mcp';
|
||||
|
||||
export type RunMCPToolQuery = {};
|
||||
|
||||
@@ -18,14 +18,9 @@ async function handler(
|
||||
): Promise<RunMCPToolResponse> {
|
||||
const { url, toolName, params } = req.body;
|
||||
|
||||
const mcpClient = getMCPClient({ url });
|
||||
const mcpClient = new MCPClient({ url });
|
||||
|
||||
try {
|
||||
const result = await mcpClient.toolCall(toolName, params);
|
||||
return result;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
return mcpClient.toolCall(toolName, params);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -85,30 +85,31 @@ const dispatchApp = async (app: AppSchema, variables: Record<string, any>) => {
|
||||
|
||||
const chatId = getNanoid();
|
||||
|
||||
const { flowUsages, assistantResponses, newVariables, flowResponses } = await dispatchWorkFlow({
|
||||
chatId,
|
||||
timezone,
|
||||
externalProvider,
|
||||
mode: 'chat',
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
uid: String(app.tmbId),
|
||||
runtimeNodes,
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
chatConfig,
|
||||
histories: [],
|
||||
stream: false,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
|
||||
});
|
||||
const { flowUsages, assistantResponses, newVariables, flowResponses, durationSeconds } =
|
||||
await dispatchWorkFlow({
|
||||
chatId,
|
||||
timezone,
|
||||
externalProvider,
|
||||
mode: 'chat',
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
uid: String(app.tmbId),
|
||||
runtimeNodes,
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
chatConfig,
|
||||
histories: [],
|
||||
stream: false,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
|
||||
});
|
||||
|
||||
// Save chat
|
||||
const aiResponse: AIChatItemType & { dataId?: string } = {
|
||||
@@ -128,7 +129,8 @@ const dispatchApp = async (app: AppSchema, variables: Record<string, any>) => {
|
||||
isUpdateUseTime: false, // owner update use time
|
||||
newTitle,
|
||||
source: ChatSourceEnum.mcp,
|
||||
content: [userQuestion, aiResponse]
|
||||
content: [userQuestion, aiResponse],
|
||||
durationSeconds
|
||||
});
|
||||
|
||||
// Push usage
|
||||
@@ -184,7 +186,7 @@ async function handler(
|
||||
const app = appList.find((app) => {
|
||||
const mcpApp = mcp.apps.find((mcpApp) => String(mcpApp.appId) === String(app._id))!;
|
||||
|
||||
return toolName === mcpApp.toolAlias || toolName === mcpApp.toolName;
|
||||
return toolName === mcpApp.toolName;
|
||||
});
|
||||
|
||||
if (!app) {
|
||||
|
||||
@@ -8,10 +8,10 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
|
||||
import { Tool } from '@modelcontextprotocol/sdk/types';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
|
||||
export type listToolsQuery = { key: string };
|
||||
|
||||
@@ -19,7 +19,9 @@ export type listToolsBody = {};
|
||||
|
||||
export type listToolsResponse = {};
|
||||
|
||||
const pluginNodes2InputSchema = (nodes: StoreNodeItemType[]) => {
|
||||
export const pluginNodes2InputSchema = (
|
||||
nodes: { flowNodeType: FlowNodeTypeEnum; inputs: FlowNodeInputItemType[] }[]
|
||||
) => {
|
||||
const pluginInput = nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput);
|
||||
|
||||
const schema: Tool['inputSchema'] = {
|
||||
@@ -47,7 +49,10 @@ const pluginNodes2InputSchema = (nodes: StoreNodeItemType[]) => {
|
||||
|
||||
return schema;
|
||||
};
|
||||
const workflow2InputSchema = (chatConfig?: AppChatConfigType) => {
|
||||
export const workflow2InputSchema = (chatConfig?: {
|
||||
fileSelectConfig?: AppChatConfigType['fileSelectConfig'];
|
||||
variables?: AppChatConfigType['variables'];
|
||||
}) => {
|
||||
const schema: Tool['inputSchema'] = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -138,7 +143,7 @@ async function handler(
|
||||
);
|
||||
|
||||
return {
|
||||
name: mcpApp.toolAlias || mcpApp.toolName,
|
||||
name: mcpApp.toolName,
|
||||
description: mcpApp.description,
|
||||
inputSchema: isPlugin
|
||||
? pluginNodes2InputSchema(version.nodes)
|
||||
@@ -150,5 +155,3 @@ async function handler(
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export { pluginNodes2InputSchema, workflow2InputSchema, handler };
|
||||
|
||||
@@ -56,15 +56,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
...req.body
|
||||
});
|
||||
|
||||
// auth app
|
||||
// const app = await MongoApp.findById(appId, 'modules').lean();
|
||||
// if (!app) {
|
||||
// throw new Error('app not found');
|
||||
// }
|
||||
// if (!whisperConfig?.open) {
|
||||
// throw new Error('Whisper is not open in the app');
|
||||
// }
|
||||
|
||||
const result = await aiTranscriptions({
|
||||
model: getDefaultSTTModel(),
|
||||
fileStream: fs.createReadStream(file.path)
|
||||
@@ -73,7 +64,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
pushWhisperUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
duration
|
||||
duration: result?.usage?.total_tokens || duration
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -93,14 +93,6 @@ type AuthResponseType = {
|
||||
};
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res.on('close', () => {
|
||||
res.end();
|
||||
});
|
||||
res.on('error', () => {
|
||||
console.log('error: ', 'request error');
|
||||
res.end();
|
||||
});
|
||||
|
||||
let {
|
||||
chatId,
|
||||
appId,
|
||||
@@ -266,42 +258,43 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
});
|
||||
|
||||
/* start flow controller */
|
||||
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
|
||||
if (app.version === 'v2') {
|
||||
return dispatchWorkFlow({
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'chat',
|
||||
timezone,
|
||||
externalProvider,
|
||||
const { flowResponses, flowUsages, assistantResponses, newVariables, durationSeconds } =
|
||||
await (async () => {
|
||||
if (app.version === 'v2') {
|
||||
return dispatchWorkFlow({
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'chat',
|
||||
timezone,
|
||||
externalProvider,
|
||||
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
uid: String(outLinkUserId || tmbId),
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
uid: String(outLinkUserId || tmbId),
|
||||
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes,
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges, interactive),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
lastInteractive: interactive,
|
||||
chatConfig,
|
||||
histories: newHistories,
|
||||
stream,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite
|
||||
});
|
||||
}
|
||||
return Promise.reject('您的工作流版本过低,请重新发布一次');
|
||||
})();
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes,
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges, interactive),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
lastInteractive: interactive,
|
||||
chatConfig,
|
||||
histories: newHistories,
|
||||
stream,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite
|
||||
});
|
||||
}
|
||||
return Promise.reject('您的工作流版本过低,请重新发布一次');
|
||||
})();
|
||||
|
||||
// save chat
|
||||
const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId);
|
||||
@@ -339,7 +332,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
appId: app._id,
|
||||
userInteractiveVal,
|
||||
aiResponse,
|
||||
newVariables
|
||||
newVariables,
|
||||
durationSeconds
|
||||
});
|
||||
} else {
|
||||
await saveChat({
|
||||
@@ -360,7 +354,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
metadata: {
|
||||
originIp,
|
||||
...metadata
|
||||
}
|
||||
},
|
||||
durationSeconds
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -93,14 +93,6 @@ type AuthResponseType = {
|
||||
};
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res.on('close', () => {
|
||||
res.end();
|
||||
});
|
||||
res.on('error', () => {
|
||||
console.log('error: ', 'request error');
|
||||
res.end();
|
||||
});
|
||||
|
||||
let {
|
||||
chatId,
|
||||
appId,
|
||||
@@ -265,44 +257,46 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
});
|
||||
|
||||
/* start flow controller */
|
||||
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
|
||||
if (app.version === 'v2') {
|
||||
return dispatchWorkFlow({
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'chat',
|
||||
timezone,
|
||||
externalProvider,
|
||||
const { flowResponses, flowUsages, assistantResponses, newVariables, durationSeconds } =
|
||||
await (async () => {
|
||||
if (app.version === 'v2') {
|
||||
return dispatchWorkFlow({
|
||||
res,
|
||||
requestOrigin: req.headers.origin,
|
||||
mode: 'chat',
|
||||
timezone,
|
||||
externalProvider,
|
||||
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
uid: String(outLinkUserId || tmbId),
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId,
|
||||
tmbId
|
||||
},
|
||||
uid: String(outLinkUserId || tmbId),
|
||||
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes,
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges, interactive),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
lastInteractive: interactive,
|
||||
chatConfig,
|
||||
histories: newHistories,
|
||||
stream,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite,
|
||||
version: 'v2',
|
||||
responseDetail
|
||||
});
|
||||
}
|
||||
return Promise.reject('您的工作流版本过低,请重新发布一次');
|
||||
})();
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
runtimeNodes,
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges, interactive),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
lastInteractive: interactive,
|
||||
chatConfig,
|
||||
histories: newHistories,
|
||||
stream,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite,
|
||||
version: 'v2',
|
||||
responseAllData,
|
||||
responseDetail
|
||||
});
|
||||
}
|
||||
return Promise.reject('您的工作流版本过低,请重新发布一次');
|
||||
})();
|
||||
|
||||
// save chat
|
||||
const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId);
|
||||
@@ -340,7 +334,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
appId: app._id,
|
||||
userInteractiveVal,
|
||||
aiResponse,
|
||||
newVariables
|
||||
newVariables,
|
||||
durationSeconds
|
||||
});
|
||||
} else {
|
||||
await saveChat({
|
||||
@@ -361,7 +356,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
metadata: {
|
||||
originIp,
|
||||
...metadata
|
||||
}
|
||||
},
|
||||
durationSeconds
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import {
|
||||
deleteDatasetDataVector,
|
||||
getVectorDataByTime
|
||||
} from '@fastgpt/service/common/vectorStore/controller';
|
||||
} from '@fastgpt/service/common/vectorDB/controller';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { MongoDatasetDataText } from '@fastgpt/service/core/dataset/data/dataTextSchema';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
|
||||
@@ -38,8 +38,9 @@ export const readConfigData = async (name: string) => {
|
||||
}
|
||||
return `data/${name}`;
|
||||
}
|
||||
// production path
|
||||
return `/app/data/${name}`;
|
||||
// Fallback to default production path
|
||||
const envPath = process.env.CONFIG_JSON_PATH || '/app/data';
|
||||
return `${envPath}/${name}`;
|
||||
})();
|
||||
|
||||
const content = await fs.promises.readFile(filename, 'utf-8');
|
||||
@@ -126,7 +127,8 @@ export async function initSystemConfig() {
|
||||
...defaultFeConfigs,
|
||||
...(dbConfig.feConfigs || {}),
|
||||
isPlus: !!FastGPTProUrl,
|
||||
show_aiproxy: !!process.env.AIPROXY_API_ENDPOINT
|
||||
show_aiproxy: !!process.env.AIPROXY_API_ENDPOINT,
|
||||
show_coupon: process.env.SHOW_COUPON === 'true'
|
||||
},
|
||||
systemEnv: {
|
||||
...fileRes.systemEnv,
|
||||
|
||||
@@ -62,32 +62,34 @@ export const getScheduleTriggerApp = async () => {
|
||||
}
|
||||
];
|
||||
|
||||
const { flowUsages, assistantResponses, flowResponses } = await retryFn(() => {
|
||||
return dispatchWorkFlow({
|
||||
chatId,
|
||||
timezone,
|
||||
externalProvider,
|
||||
mode: 'chat',
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
uid: String(app.tmbId),
|
||||
runtimeNodes: storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)),
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges),
|
||||
variables: {},
|
||||
query: userQuery,
|
||||
chatConfig,
|
||||
histories: [],
|
||||
stream: false,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
|
||||
});
|
||||
});
|
||||
const { flowUsages, assistantResponses, flowResponses, durationSeconds } = await retryFn(
|
||||
() => {
|
||||
return dispatchWorkFlow({
|
||||
chatId,
|
||||
timezone,
|
||||
externalProvider,
|
||||
mode: 'chat',
|
||||
runningAppInfo: {
|
||||
id: String(app._id),
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
runningUserInfo: {
|
||||
teamId: String(app.teamId),
|
||||
tmbId: String(app.tmbId)
|
||||
},
|
||||
uid: String(app.tmbId),
|
||||
runtimeNodes: storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)),
|
||||
runtimeEdges: storeEdges2RuntimeEdges(edges),
|
||||
variables: {},
|
||||
query: userQuery,
|
||||
chatConfig,
|
||||
histories: [],
|
||||
stream: false,
|
||||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Save chat
|
||||
await saveChat({
|
||||
@@ -111,7 +113,8 @@ export const getScheduleTriggerApp = async () => {
|
||||
value: assistantResponses,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses
|
||||
}
|
||||
]
|
||||
],
|
||||
durationSeconds
|
||||
});
|
||||
createChatUsage({
|
||||
appName: app.name,
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
PatchIndexesProps,
|
||||
UpdateDatasetDataProps
|
||||
} from '@fastgpt/global/core/dataset/controller';
|
||||
import { insertDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
|
||||
import { insertDatasetDataVector } from '@fastgpt/service/common/vectorDB/controller';
|
||||
import { jiebaSplit } from '@fastgpt/service/common/string/jieba/index';
|
||||
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
|
||||
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorDB/controller';
|
||||
import { DatasetDataIndexItemType, DatasetDataItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { getEmbeddingModel, getLLMModel } from '@fastgpt/service/core/ai/model';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import {
|
||||
deleteDatasetDataVector,
|
||||
insertDatasetDataVector
|
||||
} from '@fastgpt/service/common/vectorStore/controller';
|
||||
} from '@fastgpt/service/common/vectorDB/controller';
|
||||
import { getEmbeddingModel } from '@fastgpt/service/core/ai/model';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { DatasetTrainingSchemaType } from '@fastgpt/global/core/dataset/type';
|
||||
|
||||
@@ -279,3 +279,39 @@ export const pushWhisperUsage = ({
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
export const pushRerankUsage = ({
|
||||
teamId,
|
||||
tmbId,
|
||||
model,
|
||||
inputTokens
|
||||
}: {
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
model: string;
|
||||
inputTokens: number;
|
||||
}) => {
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model,
|
||||
inputTokens,
|
||||
modelType: ModelTypeEnum.rerank
|
||||
});
|
||||
|
||||
createUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
appName: modelName,
|
||||
totalPoints,
|
||||
source: UsageSourceEnum.fastgpt,
|
||||
list: [
|
||||
{
|
||||
moduleName: modelName,
|
||||
amount: totalPoints,
|
||||
model: modelName,
|
||||
inputTokens
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return { totalPoints };
|
||||
};
|
||||
|
||||
@@ -215,11 +215,6 @@ export const streamFetch = ({
|
||||
event,
|
||||
...parseJson
|
||||
});
|
||||
} else if (event === SseResponseEventEnum.flowNodeStatus) {
|
||||
onMessage({
|
||||
event,
|
||||
...parseJson
|
||||
});
|
||||
} else if (event === SseResponseEventEnum.flowNodeResponse) {
|
||||
onMessage({
|
||||
event,
|
||||
@@ -240,6 +235,15 @@ export const streamFetch = ({
|
||||
useSystemStore.getState().setNotSufficientModalType(TeamErrEnum.aiPointsNotEnough);
|
||||
}
|
||||
errMsg = getErrText(parseJson, '流响应错误');
|
||||
} else if (
|
||||
[SseResponseEventEnum.workflowDuration, SseResponseEventEnum.flowNodeStatus].includes(
|
||||
event as any
|
||||
)
|
||||
) {
|
||||
onMessage({
|
||||
event,
|
||||
...parseJson
|
||||
});
|
||||
}
|
||||
},
|
||||
onclose() {
|
||||
|
||||
@@ -167,13 +167,25 @@ export const useSpeech = (props?: OutLinkChatAuthProps & { appId?: string }) =>
|
||||
if (MediaRecorder.isTypeSupported('video/webm; codecs=vp9')) {
|
||||
return {
|
||||
options: { mimeType: 'video/webm; codecs=vp9' },
|
||||
filename: 'recording.mp3'
|
||||
filename: 'recording.webm'
|
||||
};
|
||||
}
|
||||
if (MediaRecorder.isTypeSupported('video/webm')) {
|
||||
return {
|
||||
options: { type: 'video/webm' },
|
||||
filename: 'recording.mp3'
|
||||
filename: 'recording.webm'
|
||||
};
|
||||
}
|
||||
if (MediaRecorder.isTypeSupported('audio/webm')) {
|
||||
return {
|
||||
options: { mimeType: 'audio/webm' },
|
||||
filename: 'recording.webm'
|
||||
};
|
||||
}
|
||||
if (MediaRecorder.isTypeSupported('audio/mp4')) {
|
||||
return {
|
||||
options: { mimeType: 'audio/mp4' },
|
||||
filename: 'recording.m4a'
|
||||
};
|
||||
}
|
||||
if (MediaRecorder.isTypeSupported('video/mp4')) {
|
||||
@@ -182,9 +194,16 @@ export const useSpeech = (props?: OutLinkChatAuthProps & { appId?: string }) =>
|
||||
filename: 'recording.mp4'
|
||||
};
|
||||
}
|
||||
if (MediaRecorder.isTypeSupported('audio/mp3')) {
|
||||
return {
|
||||
options: { mimeType: 'audio/mp3' },
|
||||
filename: 'recording.mp3'
|
||||
};
|
||||
}
|
||||
// 默认回退选项
|
||||
return {
|
||||
options: { type: 'video/webm' },
|
||||
filename: 'recording.mp3'
|
||||
options: { type: 'audio/webm' },
|
||||
filename: 'recording.webm'
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export enum EventNameEnum {
|
||||
sendQuestion = 'sendQuestion',
|
||||
editQuestion = 'editQuestion'
|
||||
editQuestion = 'editQuestion',
|
||||
openQuoteReader = 'openQuoteReader'
|
||||
}
|
||||
|
||||
export const eventBus = {
|
||||
|
||||
@@ -104,6 +104,9 @@ export const getTeamPlanStatus = () =>
|
||||
export const getTeamPlans = () =>
|
||||
GET<TeamSubSchema[]>(`/proApi/support/user/team/plan/getTeamPlans`);
|
||||
|
||||
export const redeemCoupon = (couponCode: string) =>
|
||||
GET(`/proApi/support/wallet/coupon/redeem`, { key: couponCode });
|
||||
|
||||
export const getTeamInvoiceHeader = () =>
|
||||
GET<TeamInvoiceHeaderType>(`/proApi/support/user/team/invoiceAccount/getTeamInvoiceHeader`);
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { GET, POST } from '@/web/common/api/request';
|
||||
import type { CreateBillProps, CreateBillResponse } from '@fastgpt/global/support/wallet/bill/api';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { GET, POST, PUT } from '@/web/common/api/request';
|
||||
import type {
|
||||
CheckPayResultResponse,
|
||||
CreateBillProps,
|
||||
CreateBillResponse,
|
||||
CreateOrderResponse,
|
||||
UpdatePaymentProps
|
||||
} from '@fastgpt/global/support/wallet/bill/api';
|
||||
import { BillStatusEnum, BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import type { BillSchemaType } from '@fastgpt/global/support/wallet/bill/type.d';
|
||||
import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||
|
||||
@@ -10,15 +16,22 @@ export const getBills = (
|
||||
}>
|
||||
) => POST<PaginationResponse<BillSchemaType>>(`/proApi/support/wallet/bill/list`, data);
|
||||
|
||||
export const getWxPayQRCode = (data: CreateBillProps) =>
|
||||
export const postCreatePayBill = (data: CreateBillProps) =>
|
||||
POST<CreateBillResponse>(`/proApi/support/wallet/bill/create`, data);
|
||||
|
||||
export const checkBalancePayResult = (payId: string) =>
|
||||
GET<string>(`/proApi/support/wallet/bill/checkPayResult`, { payId }).then((data) => {
|
||||
try {
|
||||
GET('/common/system/unlockTask');
|
||||
} catch (error) {}
|
||||
return data;
|
||||
});
|
||||
GET<CheckPayResultResponse>(`/proApi/support/wallet/bill/pay/checkPayResult`, { payId }).then(
|
||||
(data) => {
|
||||
try {
|
||||
if (data.status === BillStatusEnum.SUCCESS) {
|
||||
GET('/common/system/unlockTask');
|
||||
}
|
||||
} catch (error) {}
|
||||
return data;
|
||||
}
|
||||
);
|
||||
|
||||
export const putUpdatePayment = (data: UpdatePaymentProps) =>
|
||||
PUT<CreateOrderResponse>(`/proApi/support/wallet/bill/pay/updatePayment`, data);
|
||||
|
||||
export const balanceConversion = () => GET<string>(`/proApi/support/wallet/bill/balanceConversion`);
|
||||
|
||||
Reference in New Issue
Block a user