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