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:
@@ -20,13 +20,14 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
BillStatusEnum,
|
||||
BillTypeEnum,
|
||||
billPayWayMap,
|
||||
billStatusMap,
|
||||
billTypeMap
|
||||
} from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
@@ -68,39 +69,33 @@ const BillTable = () => {
|
||||
defaultRequest: false
|
||||
});
|
||||
|
||||
const { mutate: handleRefreshPayOrder, isLoading: isRefreshing } = useRequest({
|
||||
mutationFn: async (payId: string) => {
|
||||
try {
|
||||
const data = await checkBalancePayResult(payId);
|
||||
const { runAsync: handleRefreshPayOrder, loading: isRefreshing } = useRequest2(
|
||||
async (payId: string) => {
|
||||
const { status, description } = await checkBalancePayResult(payId);
|
||||
if (status === BillStatusEnum.SUCCESS) {
|
||||
toast({
|
||||
title: data,
|
||||
title: t('common:pay_success'),
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error: any) {
|
||||
} else {
|
||||
toast({
|
||||
title: error?.message,
|
||||
title: t(description as any),
|
||||
status: 'warning'
|
||||
});
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
|
||||
if (status === BillStatusEnum.SUCCESS || status === BillStatusEnum.CLOSED) {
|
||||
getData(1);
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getData(1);
|
||||
}, [billType]);
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
isLoading={isLoading || isRefreshing}
|
||||
position={'relative'}
|
||||
h={'100%'}
|
||||
minH={'50vh'}
|
||||
overflow={'overlay'}
|
||||
>
|
||||
<MyBox isLoading={isLoading} position={'relative'} h={'100%'} minH={'50vh'}>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
@@ -135,7 +130,12 @@ const BillTable = () => {
|
||||
<Td>{t(billStatusMap[item.status]?.label as any)}</Td>
|
||||
<Td>
|
||||
{item.status === 'NOTPAY' && (
|
||||
<Button mr={4} onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
|
||||
<Button
|
||||
isLoading={isRefreshing}
|
||||
mr={4}
|
||||
onClick={() => handleRefreshPayOrder(item._id)}
|
||||
size={'sm'}
|
||||
>
|
||||
{t('account_bill:update')}
|
||||
</Button>
|
||||
)}
|
||||
@@ -210,11 +210,13 @@ function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: ()
|
||||
<Box>{t(billPayWayMap[bill.metadata.payWay]?.label as any)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<FormLabel flex={'0 0 120px'}>{t('account_bill:support_wallet_amount')}:</FormLabel>
|
||||
<Box>{t('account_bill:yuan', { amount: formatStorePrice2Read(bill.price) })}</Box>
|
||||
</Flex>
|
||||
{bill.metadata && (
|
||||
{!!bill.price && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<FormLabel flex={'0 0 120px'}>{t('account_bill:support_wallet_amount')}:</FormLabel>
|
||||
<Box>{t('account_bill:yuan', { amount: formatStorePrice2Read(bill.price) })}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{bill.metadata && !!bill.price && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<FormLabel flex={'0 0 120px'}>{t('account_bill:has_invoice')}:</FormLabel>
|
||||
{bill.metadata.payWay === 'balance' ? (
|
||||
@@ -239,7 +241,7 @@ function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: ()
|
||||
{bill.metadata?.month !== undefined && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<FormLabel flex={'0 0 120px'}>{t('account_bill:subscription_mode_month')}:</FormLabel>
|
||||
<Box>{bill.metadata?.month}</Box>
|
||||
<Box>{`${bill.metadata?.month} ${t('account_bill:month')}`}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{bill.metadata?.datasetSize !== undefined && (
|
||||
|
||||
@@ -221,7 +221,7 @@ const InvoiceHeaderForm = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyBox isLoading={isLoading} pt={['1rem', '3.5rem']}>
|
||||
<MyBox isLoading={isLoading} pt={'1rem'}>
|
||||
<Flex w={'100%'} overflow={'auto'} justify={'center'} flexDir={'column'} align={'center'}>
|
||||
<InvoiceHeaderSingleForm inputForm={inputForm} />
|
||||
<Flex w={'100%'} justify={'center'} mt={'3rem'}>
|
||||
|
||||
@@ -22,6 +22,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
|
||||
const InvoiceTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const [invoiceDetailData, setInvoiceDetailData] = useState<InvoiceSchemaType | ''>('');
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { redeemCoupon } from '@/web/support/user/team/api';
|
||||
import { Button, Input, VStack, Text, ModalBody, Box, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const RedeemCouponModal = ({
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [couponCode, setCouponCode] = React.useState('');
|
||||
|
||||
const { runAsync: redeemCouponAsync, loading } = useRequest2(redeemCoupon, {
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
onClose();
|
||||
},
|
||||
successToast: t('common:common.Success')
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="support/account/coupon"
|
||||
title={t('account_info:redeem_coupon')}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box fontWeight={500} color={'myGray.900'} mb={'1'}>
|
||||
{t('account_info:redeem_coupon')}
|
||||
</Box>
|
||||
<Input
|
||||
placeholder={t('account_info:redeem_coupon')}
|
||||
value={couponCode}
|
||||
onChange={(e) => setCouponCode(e.target.value)}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('account_info:cancel')}
|
||||
</Button>
|
||||
<Button ml={2} isLoading={loading} onClick={() => redeemCouponAsync(couponCode)}>
|
||||
{t('account_info:confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default RedeemCouponModal;
|
||||
@@ -127,11 +127,11 @@ export const ModelEditModal = ({
|
||||
);
|
||||
|
||||
const priceUnit = useMemo(() => {
|
||||
if (isLLMModel || isEmbeddingModel) return '/ 1k Tokens';
|
||||
if (isLLMModel || isEmbeddingModel || isRerankModel) return '/ 1k Tokens';
|
||||
if (isTTSModel) return `/ 1k ${t('common:unit.character')}`;
|
||||
if (isSTTModel) return `/ 60 ${t('common:unit.seconds')}`;
|
||||
return '';
|
||||
}, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel]);
|
||||
}, [isLLMModel, isEmbeddingModel, isTTSModel, t, isSTTModel, isRerankModel]);
|
||||
|
||||
const { runAsync: updateModel, loading: updatingModel } = useRequest2(
|
||||
async (data: SystemModelItemType) => {
|
||||
|
||||
@@ -141,6 +141,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
typeLabel: t('common:model.type.embedding'),
|
||||
priceLabel: (
|
||||
<Flex color={'myGray.700'}>
|
||||
{`${t('common:common.Input')}: `}
|
||||
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
||||
{item.charsPointsPrice || 0}
|
||||
</Box>
|
||||
@@ -184,7 +185,17 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
.map((item) => ({
|
||||
...item,
|
||||
typeLabel: t('common:model.type.reRank'),
|
||||
priceLabel: <Flex color={'myGray.700'}>- </Flex>,
|
||||
priceLabel: item.charsPointsPrice ? (
|
||||
<Flex color={'myGray.700'}>
|
||||
{`${t('common:common.Input')}: `}
|
||||
<Box fontWeight={'bold'} color={'myGray.900'} mr={0.5}>
|
||||
{item.charsPointsPrice}
|
||||
</Box>
|
||||
{` ${t('common:support.wallet.subscription.point')} / 1K Tokens`}
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
tagColor: 'red'
|
||||
}));
|
||||
|
||||
|
||||
@@ -190,8 +190,9 @@ function EditModal({
|
||||
/>
|
||||
{isOpenContact && (
|
||||
<UpdateContact
|
||||
onClose={() => {
|
||||
onCloseContact();
|
||||
onClose={onCloseContact}
|
||||
onSuccess={(val) => {
|
||||
setValue('notificationAccount', val);
|
||||
}}
|
||||
mode="notification_account"
|
||||
/>
|
||||
|
||||
@@ -17,7 +17,7 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import { getOperationLogs } from '@/web/support/user/team/operantionLog/api';
|
||||
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
|
||||
import { operationLogI18nMap } from '@fastgpt/service/support/operationLog/constants';
|
||||
import { operationLogMap } from '@fastgpt/service/support/operationLog/constants';
|
||||
import { OperationLogEventEnum } from '@fastgpt/global/support/operationLog/constants';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
import UserBox from '@fastgpt/web/components/common/UserBox';
|
||||
@@ -52,7 +52,7 @@ function OperationLogTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
const eventOptions = useMemo(
|
||||
() =>
|
||||
Object.values(OperationLogEventEnum).map((event) => ({
|
||||
label: t(operationLogI18nMap[event].typeLabel),
|
||||
label: t(operationLogMap[event].typeLabel),
|
||||
value: event
|
||||
})),
|
||||
[t]
|
||||
@@ -158,7 +158,7 @@ function OperationLogTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{operationLogs?.map((log) => {
|
||||
const i18nData = operationLogI18nMap[log.event];
|
||||
const i18nData = operationLogMap[log.event];
|
||||
const metadata = { ...log.metadata };
|
||||
|
||||
if (log.event === OperationLogEventEnum.ASSIGN_PERMISSION) {
|
||||
|
||||
@@ -124,7 +124,7 @@ const EditForm = ({
|
||||
}
|
||||
}));
|
||||
}
|
||||
}, [selectedModel]);
|
||||
}, [selectedModel, setAppForm]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,34 +1,305 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, HStack, StackProps } from '@chakra-ui/react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
|
||||
import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comment';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
import { Node, useReactFlow } from 'reactflow';
|
||||
import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext';
|
||||
import { WorkflowEventContext } from '../../context/workflowEventContext';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import dagre from '@dagrejs/dagre';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { WorkflowStatusContext } from '../../context/workflowStatusContext';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
|
||||
const ContextMenu = () => {
|
||||
const { t } = useTranslation();
|
||||
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes);
|
||||
const menu = useContextSelector(WorkflowEventContext, (v) => v.menu);
|
||||
const menu = useContextSelector(WorkflowEventContext, (v) => v.menu!);
|
||||
const setMenu = useContextSelector(WorkflowEventContext, (ctx) => ctx.setMenu);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const setEdges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setEdges);
|
||||
const getParentNodeSizeAndPosition = useContextSelector(
|
||||
WorkflowStatusContext,
|
||||
(v) => v.getParentNodeSizeAndPosition
|
||||
);
|
||||
|
||||
const { screenToFlowPosition } = useReactFlow();
|
||||
const newNode = nodeTemplate2FlowNode({
|
||||
template: CommentNode,
|
||||
position: screenToFlowPosition({ x: menu?.left ?? 0, y: menu?.top ?? 0 }),
|
||||
t
|
||||
});
|
||||
const { fitView, screenToFlowPosition } = useReactFlow();
|
||||
|
||||
const allUnFolded = useMemo(() => {
|
||||
return !!menu ? nodeList.some((node) => node.isFolded) : false;
|
||||
}, [nodeList, menu]);
|
||||
|
||||
return !!menu ? (
|
||||
const onLayout = useCallback(() => {
|
||||
const updateChildNodesPosition = ({
|
||||
startNode,
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
startNode: Node<FlowNodeItemType>;
|
||||
nodes: Node<FlowNodeItemType>[];
|
||||
edges: any[];
|
||||
}) => {
|
||||
const startPosition = { x: startNode.position.x, y: startNode.position.y };
|
||||
|
||||
const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
dagreGraph.setGraph({
|
||||
rankdir: 'LR',
|
||||
nodesep: 80, // Horizontal space
|
||||
ranksep: 120 // Vertical space
|
||||
});
|
||||
|
||||
nodes.forEach((node) => {
|
||||
dagreGraph.setNode(node.id, { width: node.width!, height: node.height! });
|
||||
});
|
||||
|
||||
// Find connected nodes
|
||||
const connectedNodeIds = new Set<string>();
|
||||
edges.forEach((edge) => {
|
||||
connectedNodeIds.add(edge.source);
|
||||
connectedNodeIds.add(edge.target);
|
||||
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
const layoutedStartNode = dagreGraph.node(startNode.data.nodeId);
|
||||
const offsetX = startPosition.x - (layoutedStartNode.x - startNode.width! / 2);
|
||||
const offsetY = startPosition.y - (layoutedStartNode.y - startNode.height! / 2);
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (!connectedNodeIds.has(node.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
|
||||
node.position = {
|
||||
x: nodeWithPosition.x - node.width! / 2 + offsetX,
|
||||
y: nodeWithPosition.y - node.height! / 2 + offsetY
|
||||
};
|
||||
});
|
||||
};
|
||||
const updateParentNodesPosition = ({
|
||||
startNode,
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
startNode: Node<FlowNodeItemType>;
|
||||
nodes: Node<FlowNodeItemType>[];
|
||||
edges: any[];
|
||||
}) => {
|
||||
const startPosition = { x: startNode.position.x, y: startNode.position.y };
|
||||
|
||||
const childNodeIdsSet = new Set(
|
||||
nodes.filter((node) => !!node.data.parentNodeId).map((node) => node.data.nodeId)
|
||||
);
|
||||
|
||||
const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
dagreGraph.setGraph({
|
||||
rankdir: 'LR',
|
||||
nodesep: 80, // Horizontal space
|
||||
ranksep: 120 // Vertical space
|
||||
});
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (childNodeIdsSet.has(node.data.nodeId)) return;
|
||||
dagreGraph.setNode(node.id, { width: node.width!, height: node.height! });
|
||||
});
|
||||
|
||||
// Find connected nodes
|
||||
const connectedNodeIds = new Set<string>();
|
||||
edges.forEach((edge) => {
|
||||
if (childNodeIdsSet.has(edge.source)) return;
|
||||
if (childNodeIdsSet.has(edge.target)) return;
|
||||
|
||||
connectedNodeIds.add(edge.source);
|
||||
connectedNodeIds.add(edge.target);
|
||||
|
||||
dagreGraph.setEdge(edge.source, edge.target);
|
||||
});
|
||||
|
||||
dagre.layout(dagreGraph);
|
||||
const layoutedStartNode = dagreGraph.node(startNode.data.nodeId);
|
||||
const offsetX = startPosition.x - (layoutedStartNode.x - startNode.width! / 2);
|
||||
const offsetY = startPosition.y - (layoutedStartNode.y - startNode.height! / 2);
|
||||
|
||||
nodes.forEach((node) => {
|
||||
if (!connectedNodeIds.has(node.id) || childNodeIdsSet.has(node.data.nodeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeWithPosition = dagreGraph.node(node.id);
|
||||
const targetX = nodeWithPosition.x - node.width! / 2 + offsetX;
|
||||
const targetY = nodeWithPosition.y - node.height! / 2 + offsetY;
|
||||
const diffX = targetX - node.position.x;
|
||||
const diffY = targetY - node.position.y;
|
||||
node.position = {
|
||||
x: targetX,
|
||||
y: targetY
|
||||
};
|
||||
|
||||
// Update child nodes position
|
||||
nodes.forEach((childNode) => {
|
||||
if (childNode.data.parentNodeId === node.data.nodeId) {
|
||||
childNode.position = {
|
||||
x: childNode.position.x + diffX,
|
||||
y: childNode.position.y + diffY
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
setNodes((nodes) => {
|
||||
let newNodes = cloneDeep(nodes);
|
||||
|
||||
setEdges((edges) => {
|
||||
const childNodesIdSet = new Set();
|
||||
|
||||
// 1. Layout child nodes
|
||||
const childNodesMap: Record<string, Node<FlowNodeItemType>[]> = {};
|
||||
newNodes.forEach((node) => {
|
||||
const parentId = node.data.parentNodeId;
|
||||
if (parentId) {
|
||||
childNodesIdSet.add(parentId);
|
||||
if (!childNodesMap[parentId]) {
|
||||
childNodesMap[parentId] = [];
|
||||
}
|
||||
childNodesMap[parentId].push(node);
|
||||
}
|
||||
});
|
||||
const childNodesArr = Object.values(childNodesMap);
|
||||
if (childNodesArr.length > 0) {
|
||||
childNodesArr.forEach((childNodes) => {
|
||||
updateChildNodesPosition({
|
||||
startNode: childNodes[0],
|
||||
nodes: childNodes,
|
||||
edges
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Reset parent node size and position
|
||||
const parentNodes = newNodes.filter((node) => childNodesIdSet.has(node.data.nodeId));
|
||||
parentNodes.forEach((node) => {
|
||||
const res = getParentNodeSizeAndPosition({
|
||||
nodes: newNodes,
|
||||
parentId: node.data.nodeId
|
||||
});
|
||||
if (!res) return;
|
||||
const { parentX, parentY, nodeWidth, nodeHeight, childHeight, childWidth } = res;
|
||||
|
||||
node.position = {
|
||||
x: parentX,
|
||||
y: parentY
|
||||
};
|
||||
node.width = nodeWidth;
|
||||
node.height = nodeHeight;
|
||||
node.data.inputs.forEach((input) => {
|
||||
if (input.key === NodeInputKeyEnum.nodeHeight) {
|
||||
input.value = childHeight;
|
||||
} else if (input.key === NodeInputKeyEnum.nodeWidth) {
|
||||
input.value = childWidth;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 3. Layout parent node
|
||||
updateParentNodesPosition({
|
||||
startNode:
|
||||
newNodes.find((node) =>
|
||||
[
|
||||
FlowNodeTypeEnum.systemConfig,
|
||||
FlowNodeTypeEnum.pluginConfig,
|
||||
FlowNodeTypeEnum.workflowStart,
|
||||
FlowNodeTypeEnum.pluginInput
|
||||
].includes(node.data.flowNodeType)
|
||||
) || newNodes[0],
|
||||
nodes: newNodes,
|
||||
edges
|
||||
});
|
||||
return edges;
|
||||
});
|
||||
|
||||
return newNodes;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
fitView();
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onAddComment = useCallback(() => {
|
||||
const newNode = nodeTemplate2FlowNode({
|
||||
template: CommentNode,
|
||||
position: screenToFlowPosition({ x: menu?.left ?? 0, y: (menu?.top ?? 0) + 100 }),
|
||||
t
|
||||
});
|
||||
|
||||
setNodes((state) => {
|
||||
const newState = state
|
||||
.map((node) => ({
|
||||
...node,
|
||||
selected: false
|
||||
}))
|
||||
// @ts-ignore
|
||||
.concat(newNode);
|
||||
return newState;
|
||||
});
|
||||
}, [menu]);
|
||||
|
||||
const onFold = useCallback(() => {
|
||||
setNodes((state) => {
|
||||
return state.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isFolded: !allUnFolded
|
||||
}
|
||||
}));
|
||||
});
|
||||
}, [allUnFolded]);
|
||||
|
||||
const ContextMenuItem = useCallback(
|
||||
({
|
||||
icon,
|
||||
label,
|
||||
onClick,
|
||||
...props
|
||||
}: {
|
||||
icon: string;
|
||||
label: string;
|
||||
onClick: () => any;
|
||||
} & StackProps) => {
|
||||
return (
|
||||
<HStack
|
||||
px={2}
|
||||
py={1}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'sm'}
|
||||
_hover={{ bg: 'myGray.50', color: 'primary.500' }}
|
||||
onClick={() => {
|
||||
onClick();
|
||||
setMenu(null);
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<MyIcon name={icon as any} w={'1rem'} ml={1} />
|
||||
<Box fontSize={'sm'} fontWeight={'500'}>
|
||||
{label}
|
||||
</Box>
|
||||
</HStack>
|
||||
);
|
||||
},
|
||||
[setMenu]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
<Box
|
||||
position="absolute"
|
||||
@@ -55,61 +326,26 @@ const ContextMenu = () => {
|
||||
p={1}
|
||||
zIndex={10}
|
||||
>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={2}
|
||||
py={1}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'sm'}
|
||||
_hover={{ bg: 'myGray.50', color: 'primary.500' }}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
setNodes((state) => {
|
||||
const newState = state
|
||||
.map((node) => ({
|
||||
...node,
|
||||
selected: false
|
||||
}))
|
||||
// @ts-ignore
|
||||
.concat(newNode);
|
||||
return newState;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name="comment" w={'1rem'} ml={1} />
|
||||
<Box fontSize={'12px'} fontWeight={'500'} ml={1.5}>
|
||||
{t('workflow:context_menu.add_comment')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex
|
||||
mt={1}
|
||||
alignItems={'center'}
|
||||
px={2}
|
||||
py={1}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'sm'}
|
||||
_hover={{ bg: 'myGray.50', color: 'primary.500' }}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
setNodes((state) => {
|
||||
return state.map((node) => ({
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
isFolded: !allUnFolded
|
||||
}
|
||||
}));
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyIcon name="common/select" w={'1rem'} ml={1} />
|
||||
<Box fontSize={'12px'} fontWeight={'500'} ml={1.5}>
|
||||
{allUnFolded ? t('workflow:unFoldAll') : t('workflow:foldAll')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<ContextMenuItem
|
||||
mb={1}
|
||||
icon="alignLeft"
|
||||
label={t('workflow:auto_align')}
|
||||
onClick={onLayout}
|
||||
/>
|
||||
<ContextMenuItem
|
||||
mb={1}
|
||||
icon="comment"
|
||||
label={t('workflow:context_menu.add_comment')}
|
||||
onClick={onAddComment}
|
||||
/>
|
||||
<ContextMenuItem
|
||||
icon="common/select"
|
||||
label={allUnFolded ? t('workflow:unFoldAll') : t('workflow:foldAll')}
|
||||
onClick={onFold}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null;
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ContextMenu);
|
||||
|
||||
@@ -73,6 +73,7 @@ const Workflow = () => {
|
||||
WorkflowEventContext,
|
||||
(v) => v.workflowControlMode
|
||||
);
|
||||
const menu = useContextSelector(WorkflowEventContext, (v) => v.menu);
|
||||
|
||||
const {
|
||||
handleNodesChange,
|
||||
@@ -163,7 +164,7 @@ const Workflow = () => {
|
||||
: {})}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
>
|
||||
<ContextMenu />
|
||||
{!!menu && <ContextMenu />}
|
||||
<FlowController />
|
||||
<HelperLines horizontal={helperLineHorizontal} vertical={helperLineVertical} />
|
||||
</ReactFlow>
|
||||
|
||||
@@ -26,7 +26,7 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5,
|
||||
usingReRank: false
|
||||
usingReRank: true
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -27,7 +27,7 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
|
||||
embeddingWeight: 0.5,
|
||||
limit: 3000,
|
||||
similarity: 0.5,
|
||||
usingReRank: false,
|
||||
usingReRank: true,
|
||||
rerankModel: defaultModels.llm?.model,
|
||||
rerankWeight: 0.6,
|
||||
datasetSearchUsingExtensionQuery: true,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AppContext } from '../../context';
|
||||
import { compareSnapshot } from '@/web/core/workflow/utils';
|
||||
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getNodesBounds, Node } from 'reactflow';
|
||||
import { Node } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
@@ -20,6 +20,22 @@ type WorkflowStatusContextType = {
|
||||
isSaved: boolean;
|
||||
leaveSaveSign: React.MutableRefObject<boolean>;
|
||||
resetParentNodeSizeAndPosition: (parentId: string) => void;
|
||||
getParentNodeSizeAndPosition: ({
|
||||
nodes,
|
||||
parentId
|
||||
}: {
|
||||
nodes: Node<FlowNodeItemType>[];
|
||||
parentId: string;
|
||||
}) =>
|
||||
| {
|
||||
parentX: number;
|
||||
parentY: number;
|
||||
childWidth: number;
|
||||
childHeight: number;
|
||||
nodeWidth: number;
|
||||
nodeHeight: number;
|
||||
}
|
||||
| undefined;
|
||||
};
|
||||
|
||||
export const WorkflowStatusContext = createContext<WorkflowStatusContextType>({
|
||||
@@ -94,30 +110,70 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
|
||||
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const getParentNodeSizeAndPosition = useMemoizedFn(
|
||||
({ nodes, parentId }: { nodes: Node<FlowNodeItemType>[]; parentId: string }) => {
|
||||
const { childNodes, loopNode } = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.data.parentNodeId === parentId) {
|
||||
acc.childNodes.push(node);
|
||||
}
|
||||
if (node.id === parentId) {
|
||||
acc.loopNode = node;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
|
||||
);
|
||||
|
||||
if (!loopNode) return;
|
||||
const loopChilWidth =
|
||||
loopNode.data.inputs.find((node) => node.key === NodeInputKeyEnum.nodeWidth)?.value ?? 0;
|
||||
const loopChilHeight =
|
||||
loopNode.data.inputs.find((node) => node.key === NodeInputKeyEnum.nodeHeight)?.value ?? 0;
|
||||
|
||||
// 初始化为第一个节点的边界
|
||||
let minX = childNodes[0].position.x;
|
||||
let minY = childNodes[0].position.y;
|
||||
let maxX = childNodes[0].position.x + (childNodes[0].width || 0);
|
||||
let maxY = childNodes[0].position.y + (childNodes[0].height || 0);
|
||||
|
||||
// 遍历所有节点找出最小/最大边界
|
||||
childNodes.forEach((node) => {
|
||||
const nodeWidth = node.width || 0;
|
||||
const nodeHeight = node.height || 0;
|
||||
|
||||
minX = Math.min(minX, node.position.x);
|
||||
minY = Math.min(minY, node.position.y);
|
||||
maxX = Math.max(maxX, node.position.x + nodeWidth);
|
||||
maxY = Math.max(maxY, node.position.y + nodeHeight);
|
||||
});
|
||||
|
||||
const childWidth = Math.max(maxX - minX + 80, 840);
|
||||
const childHeight = Math.max(maxY - minY + 80, 600);
|
||||
|
||||
const diffWidth = childWidth - loopChilWidth;
|
||||
const diffHeight = childHeight - loopChilHeight;
|
||||
const targetNodeWidth = (loopNode.width ?? 0) + diffWidth;
|
||||
const targetNodeHeight = (loopNode.height ?? 0) + diffHeight;
|
||||
|
||||
const offsetHeight =
|
||||
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
|
||||
?.value ?? 83;
|
||||
|
||||
return {
|
||||
parentX: Math.round(minX - 70),
|
||||
parentY: Math.round(minY - offsetHeight - 240),
|
||||
childWidth,
|
||||
childHeight,
|
||||
nodeWidth: targetNodeWidth,
|
||||
nodeHeight: targetNodeHeight
|
||||
};
|
||||
}
|
||||
);
|
||||
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
|
||||
const { childNodes, loopNode } = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.data.parentNodeId === parentId) {
|
||||
acc.childNodes.push(node);
|
||||
}
|
||||
if (node.id === parentId) {
|
||||
acc.loopNode = node;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
|
||||
);
|
||||
|
||||
if (!loopNode) return;
|
||||
|
||||
const rect = getNodesBounds(childNodes);
|
||||
// Calculate parent node size with minimum width/height constraints
|
||||
const width = Math.max(rect.width + 80, 840);
|
||||
const height = Math.max(rect.height + 80, 600);
|
||||
|
||||
const offsetHeight =
|
||||
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
|
||||
?.value ?? 83;
|
||||
const res = getParentNodeSizeAndPosition({ nodes, parentId });
|
||||
if (!res) return;
|
||||
const { parentX, parentY, childWidth, childHeight } = res;
|
||||
|
||||
// Update parentNode size and position
|
||||
onChangeNode({
|
||||
@@ -126,7 +182,7 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
key: NodeInputKeyEnum.nodeWidth,
|
||||
value: {
|
||||
...Input_Template_Node_Width,
|
||||
value: width
|
||||
value: childWidth
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
@@ -135,7 +191,7 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
key: NodeInputKeyEnum.nodeHeight,
|
||||
value: {
|
||||
...Input_Template_Node_Height,
|
||||
value: height
|
||||
value: childHeight
|
||||
}
|
||||
});
|
||||
// Update parentNode position
|
||||
@@ -144,8 +200,8 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
id: parentId,
|
||||
type: 'position',
|
||||
position: {
|
||||
x: Math.round(rect.x - 70),
|
||||
y: Math.round(rect.y - offsetHeight - 240)
|
||||
x: parentX,
|
||||
y: parentY
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -155,9 +211,10 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
return {
|
||||
isSaved,
|
||||
leaveSaveSign,
|
||||
resetParentNodeSizeAndPosition
|
||||
resetParentNodeSizeAndPosition,
|
||||
getParentNodeSizeAndPosition
|
||||
};
|
||||
}, [isSaved, resetParentNodeSizeAndPosition]);
|
||||
}, [isSaved, resetParentNodeSizeAndPosition, getParentNodeSizeAndPosition]);
|
||||
return (
|
||||
<WorkflowStatusContext.Provider value={contextValue}>{children}</WorkflowStatusContext.Provider>
|
||||
);
|
||||
|
||||
@@ -35,6 +35,7 @@ import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||
import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { postCreateMcpServer, putUpdateMcpServer } from '../../../web/support/mcp/api';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
|
||||
export type EditMcForm = {
|
||||
id?: string;
|
||||
@@ -62,7 +63,7 @@ const SelectAppModal = ({
|
||||
{
|
||||
appId: string;
|
||||
toolName: string;
|
||||
toolAlias: string;
|
||||
appName: string;
|
||||
avatar: string;
|
||||
description: string;
|
||||
}[]
|
||||
@@ -76,7 +77,7 @@ const SelectAppModal = ({
|
||||
data.map((item) => ({
|
||||
appId: item.id,
|
||||
toolName: item.name,
|
||||
toolAlias: item.name,
|
||||
appName: item.name,
|
||||
avatar: item.avatar,
|
||||
description: selectedApps.find((app) => app.appId === item.id)?.description || ''
|
||||
}))
|
||||
@@ -176,7 +177,7 @@ const SelectAppModal = ({
|
||||
{
|
||||
appId: item._id,
|
||||
toolName: item.name,
|
||||
toolAlias: item.name,
|
||||
appName: item.name,
|
||||
avatar: item.avatar,
|
||||
description: item.intro
|
||||
}
|
||||
@@ -281,7 +282,7 @@ const EditMcpModal = ({
|
||||
apps: data.apps.map((item) => ({
|
||||
appId: item.appId,
|
||||
toolName: item.toolName,
|
||||
toolAlias: item.toolAlias,
|
||||
appName: item.appName,
|
||||
description: item.description
|
||||
}))
|
||||
}),
|
||||
@@ -299,7 +300,7 @@ const EditMcpModal = ({
|
||||
apps: data.apps.map((item) => ({
|
||||
appId: item.appId,
|
||||
toolName: item.toolName,
|
||||
toolAlias: item.toolAlias,
|
||||
appName: item.appName,
|
||||
description: item.description
|
||||
}))
|
||||
}),
|
||||
@@ -317,7 +318,7 @@ const EditMcpModal = ({
|
||||
iconSrc="key"
|
||||
title={isEdit ? t('dashboard_mcp:edit_mcp') : t('dashboard_mcp:create_mcp')}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '600px']}
|
||||
maxW={['90vw', '800px']}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
>
|
||||
@@ -339,8 +340,11 @@ const EditMcpModal = ({
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>
|
||||
{t('dashboard_mcp:tool_name')}
|
||||
<QuestionTip label={t('dashboard_mcp:tool_name_tip')} />
|
||||
</Th>
|
||||
<Th>{t('dashboard_mcp:app_name')}</Th>
|
||||
<Th>{t('dashboard_mcp:app_tool_name')}</Th>
|
||||
<Th>{t('dashboard_mcp:app_description')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
@@ -349,15 +353,15 @@ const EditMcpModal = ({
|
||||
{apps.map((app, index) => {
|
||||
return (
|
||||
<Tr key={app.id} fontWeight={500} fontSize={'mini'} color={'myGray.900'}>
|
||||
<Td>{app.toolName}</Td>
|
||||
<Td>
|
||||
<Input
|
||||
{...register(`apps.${index}.toolAlias`)}
|
||||
placeholder={app.toolName}
|
||||
{...register(`apps.${index}.toolName`, { required: true })}
|
||||
placeholder={t('dashboard_mcp:tool_name_placeholder')}
|
||||
bg={'myGray.50'}
|
||||
w={'100%'}
|
||||
/>
|
||||
</Td>
|
||||
<Td>{app.appName}</Td>
|
||||
<Td>
|
||||
<Input
|
||||
{...register(`apps.${index}.description`, { required: true })}
|
||||
@@ -413,7 +417,7 @@ const EditMcpModal = ({
|
||||
e.map((item) => ({
|
||||
appId: item.appId,
|
||||
toolName: item.toolName,
|
||||
toolAlias: item.toolAlias,
|
||||
appName: item.appName,
|
||||
description: item.description
|
||||
}))
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
||||
searchParams: {
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
embeddingWeight: 0.5,
|
||||
usingReRank: false,
|
||||
usingReRank: true,
|
||||
rerankModel: defaultModels?.rerank?.model,
|
||||
rerankWeight: 0.5,
|
||||
limit: 5000,
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import { Box, Flex, Grid, Button, VStack } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
|
||||
import { postCreatePayBill } from '@/web/support/wallet/bill/api';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { subPlans } = useSystemStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
|
||||
// extra dataset
|
||||
@@ -26,40 +25,33 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
month: 1
|
||||
}
|
||||
});
|
||||
const onclickBuyDatasetSize = useCallback(
|
||||
const { runAsync: onclickBuyDatasetSize, loading: isLoadingBuyDatasetSize } = useRequest2(
|
||||
async ({ datasetSize, month }: { datasetSize: number; month: number }) => {
|
||||
try {
|
||||
datasetSize = Math.ceil(datasetSize);
|
||||
month = Math.ceil(month);
|
||||
datasetSize = Math.ceil(datasetSize);
|
||||
month = Math.ceil(month);
|
||||
|
||||
const datasetSizePayAmount = datasetSize * month * extraDatasetPrice;
|
||||
if (datasetSizePayAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.wallet.amount_0')
|
||||
});
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.extraDatasetSub,
|
||||
month,
|
||||
extraDatasetSize: datasetSize
|
||||
});
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
status: 'error'
|
||||
const datasetSizePayAmount = datasetSize * month * extraDatasetPrice;
|
||||
if (datasetSizePayAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.wallet.amount_0')
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
const res = await postCreatePayBill({
|
||||
type: BillTypeEnum.extraDatasetSub,
|
||||
month,
|
||||
extraDatasetSize: datasetSize
|
||||
});
|
||||
setQRPayData({
|
||||
tip: t('common:button.extra_dataset_size_tip'),
|
||||
...res
|
||||
});
|
||||
},
|
||||
[extraDatasetPrice, toast]
|
||||
{
|
||||
manual: true,
|
||||
refreshDeps: [extraDatasetPrice]
|
||||
}
|
||||
);
|
||||
|
||||
// extra ai points
|
||||
@@ -70,41 +62,34 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
month: 1
|
||||
}
|
||||
});
|
||||
const onclickBuyExtraPoints = useCallback(
|
||||
const { runAsync: onclickBuyExtraPoints, loading: isLoadingBuyExtraPoints } = useRequest2(
|
||||
async ({ points }: { points: number }) => {
|
||||
try {
|
||||
points = Math.ceil(points);
|
||||
points = Math.ceil(points);
|
||||
|
||||
const month = 1;
|
||||
const payAmount = points * month * extraPointsPrice;
|
||||
const month = 1;
|
||||
const payAmount = points * month * extraPointsPrice;
|
||||
|
||||
if (payAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.wallet.amount_0')
|
||||
});
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.extraPoints,
|
||||
extraPoints: points
|
||||
});
|
||||
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
status: 'error'
|
||||
if (payAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.wallet.amount_0')
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
const res = await postCreatePayBill({
|
||||
type: BillTypeEnum.extraPoints,
|
||||
extraPoints: points
|
||||
});
|
||||
|
||||
setQRPayData({
|
||||
tip: t('common:button.extra_points_tip'),
|
||||
...res
|
||||
});
|
||||
},
|
||||
[extraPointsPrice, toast]
|
||||
{
|
||||
manual: true,
|
||||
refreshDeps: [extraPointsPrice]
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -184,7 +169,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={loading}
|
||||
isLoading={isLoadingBuyDatasetSize}
|
||||
onClick={handleSubmitDatasetSize(onclickBuyDatasetSize)}
|
||||
color={'primary.700'}
|
||||
>
|
||||
@@ -264,7 +249,7 @@ const ExtraPlan = ({ onPaySuccess }: { onPaySuccess?: () => void }) => {
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={loading}
|
||||
isLoading={isLoadingBuyExtraPoints}
|
||||
onClick={handleSubmitExtraPoints(onclickBuyExtraPoints)}
|
||||
color={'primary.700'}
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constant
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
|
||||
import { postCreatePayBill } from '@/web/support/wallet/bill/api';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||
|
||||
@@ -53,9 +53,9 @@ const Standard = ({
|
||||
permissionCustomApiKey: value.permissionCustomApiKey,
|
||||
permissionCustomCopyright: value.permissionCustomCopyright,
|
||||
trainingWeight: value.trainingWeight,
|
||||
permissionReRank: value.permissionReRank,
|
||||
totalPoints: value.totalPoints * (selectSubMode === SubModeEnum.month ? 1 : 12),
|
||||
permissionWebsiteSync: value.permissionWebsiteSync
|
||||
permissionWebsiteSync: value.permissionWebsiteSync,
|
||||
permissionTeamOperationLog: value.permissionTeamOperationLog
|
||||
};
|
||||
})
|
||||
: [];
|
||||
@@ -65,13 +65,9 @@ const Standard = ({
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
|
||||
/* Get pay code */
|
||||
const { runAsync: onPay, loading: isLoading } = useRequest2(getWxPayQRCode, {
|
||||
const { runAsync: onPay, loading: isLoading } = useRequest2(postCreatePayBill, {
|
||||
onSuccess(res) {
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
setQRPayData(res);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user