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:
Archer
2025-04-26 16:17:21 +08:00
committed by GitHub
parent a669a60fe6
commit 0720bbe4da
143 changed files with 2067 additions and 1093 deletions

View File

@@ -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>

View File

@@ -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'
}));

View File

@@ -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')} />
)}

View File

@@ -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={{

View File

@@ -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
}
);

View File

@@ -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>
)}

View File

@@ -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;

View File

@@ -17,6 +17,7 @@ export type generatingMessageProps = {
interactive?: WorkflowInteractiveResponseType;
variables?: Record<string, any>;
nodeResponse?: ChatHistoryItemResType;
durationSeconds?: number;
};
export type StartChatFnProps = {

View File

@@ -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 && (
<>

View File

@@ -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')

View File

@@ -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>
);

View File

@@ -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;
};

View File

@@ -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
};
}

View File

@@ -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'),

View File

@@ -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 && (

View File

@@ -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'}>

View File

@@ -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 | ''>('');

View File

@@ -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;

View File

@@ -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) => {

View File

@@ -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'
}));

View File

@@ -190,8 +190,9 @@ function EditModal({
/>
{isOpenContact && (
<UpdateContact
onClose={() => {
onCloseContact();
onClose={onCloseContact}
onSuccess={(val) => {
setValue('notificationAccount', val);
}}
mode="notification_account"
/>

View File

@@ -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) {

View File

@@ -124,7 +124,7 @@ const EditForm = ({
}
}));
}
}, [selectedModel]);
}, [selectedModel, setAppForm]);
return (
<>

View File

@@ -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);

View File

@@ -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>

View File

@@ -26,7 +26,7 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
searchMode: DatasetSearchModeEnum.embedding,
limit: 5,
similarity: 0.5,
usingReRank: false
usingReRank: true
});
const {

View File

@@ -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,

View File

@@ -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>
);

View File

@@ -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
}))
);

View File

@@ -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,

View File

@@ -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'}
>

View File

@@ -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);
}
});

View File

@@ -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;
};

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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 () => {

View File

@@ -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 () => {

View File

@@ -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';

View File

@@ -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
});
}

View File

@@ -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({

View File

@@ -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> {

View File

@@ -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
};
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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 };

View File

@@ -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, {

View File

@@ -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
});
}

View File

@@ -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
});
}

View File

@@ -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';

View File

@@ -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,

View File

@@ -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,

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 };
};

View File

@@ -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() {

View File

@@ -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'
};
})();

View File

@@ -1,6 +1,7 @@
export enum EventNameEnum {
sendQuestion = 'sendQuestion',
editQuestion = 'editQuestion'
editQuestion = 'editQuestion',
openQuoteReader = 'openQuoteReader'
}
export const eventBus = {

View File

@@ -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`);

View File

@@ -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`);