@@ -8,10 +8,10 @@ import Tabs from '../Tabs';
|
||||
import MyModal from '../MyModal';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import Markdown from '../Markdown';
|
||||
import { QuoteList } from './QuoteModal';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
|
||||
function Row({
|
||||
label,
|
||||
@@ -131,10 +131,10 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
<>
|
||||
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
||||
{activeModule?.totalPoints !== undefined && (
|
||||
{activeModule?.price !== undefined && (
|
||||
<Row
|
||||
label={t('support.wallet.usage.Total points')}
|
||||
value={formatNumber(activeModule.totalPoints)}
|
||||
label={t('core.chat.response.module price')}
|
||||
value={`¥${formatStorePrice2Read(activeModule?.price)}`}
|
||||
/>
|
||||
)}
|
||||
<Row
|
||||
@@ -142,9 +142,11 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
value={`${activeModule?.runningTime || 0}s`}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
||||
<Row label={t('wallet.bill.Chars length')} value={`${activeModule?.charsLength}`} />
|
||||
<Row label={t('wallet.bill.Input Token Length')} value={`${activeModule?.inputTokens}`} />
|
||||
<Row
|
||||
label={t('support.wallet.usage.Chars length')}
|
||||
value={`${activeModule?.charsLength}`}
|
||||
label={t('wallet.bill.Output Token Length')}
|
||||
value={`${activeModule?.outputTokens}`}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||
<Row
|
||||
@@ -206,14 +208,14 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
|
||||
<Row
|
||||
label={t('core.chat.response.search using reRank')}
|
||||
value={`${activeModule?.searchUsingReRank}`}
|
||||
value={activeModule?.searchUsingReRank}
|
||||
/>
|
||||
<Row
|
||||
label={t('core.chat.response.Extension model')}
|
||||
value={activeModule?.extensionModel}
|
||||
/>
|
||||
<Row
|
||||
label={t('support.wallet.usage.Extension result')}
|
||||
label={t('wallet.bill.Extension result')}
|
||||
value={`${activeModule?.extensionResult}`}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -11,7 +11,6 @@ const unAuthPage: { [key: string]: boolean } = {
|
||||
'/login/fastlogin': true,
|
||||
'/appStore': true,
|
||||
'/chat/share': true,
|
||||
'/chat/team': true,
|
||||
'/tools/price': true,
|
||||
'/price': true
|
||||
};
|
||||
|
||||
@@ -23,7 +23,6 @@ const pcUnShowLayoutRoute: Record<string, boolean> = {
|
||||
'/login/provider': true,
|
||||
'/login/fastlogin': true,
|
||||
'/chat/share': true,
|
||||
'/chat/team': true,
|
||||
'/app/edit': true,
|
||||
'/chat': true,
|
||||
'/tools/price': true,
|
||||
@@ -35,7 +34,6 @@ const phoneUnShowLayoutRoute: Record<string, boolean> = {
|
||||
'/login/provider': true,
|
||||
'/login/fastlogin': true,
|
||||
'/chat/share': true,
|
||||
'/chat/team': true,
|
||||
'/tools/price': true,
|
||||
'/price': true
|
||||
};
|
||||
@@ -116,10 +114,9 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!!userInfo && <UpdateInviteModal />}
|
||||
</Box>
|
||||
<Loading loading={loading} zIndex={999999} />
|
||||
{!!userInfo && <UpdateInviteModal />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
|
||||
className={`markdown ${styles.markdown}
|
||||
${isChatting ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''}
|
||||
`}
|
||||
remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]}
|
||||
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||
rehypePlugins={[RehypeKatex]}
|
||||
components={components}
|
||||
linkTarget={'_blank'}
|
||||
|
||||
@@ -2,15 +2,15 @@ import React, { useMemo } from 'react';
|
||||
|
||||
import MySelect, { type SelectProps } from './index';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useDisclosure } from '@chakra-ui/react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
|
||||
|
||||
const PriceBox = dynamic(() => import('@/components/support/wallet/Price'));
|
||||
|
||||
const SelectAiModel = ({ list, ...props }: SelectProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const router = useRouter();
|
||||
|
||||
const expandList = useMemo(() => {
|
||||
return feConfigs.show_pay
|
||||
? list.concat({
|
||||
@@ -20,6 +20,12 @@ const SelectAiModel = ({ list, ...props }: SelectProps) => {
|
||||
: list;
|
||||
}, [feConfigs.show_pay, list, t]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenPriceBox,
|
||||
onOpen: onOpenPriceBox,
|
||||
onClose: onClosePriceBox
|
||||
} = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MySelect
|
||||
@@ -27,12 +33,13 @@ const SelectAiModel = ({ list, ...props }: SelectProps) => {
|
||||
{...props}
|
||||
onchange={(e) => {
|
||||
if (e === 'price') {
|
||||
router.push(AI_POINT_USAGE_CARD_ROUTE);
|
||||
onOpenPriceBox();
|
||||
return;
|
||||
}
|
||||
props.onchange?.(e);
|
||||
}}
|
||||
/>
|
||||
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItemOption,
|
||||
MenuOptionGroup,
|
||||
Flex,
|
||||
TagLabel,
|
||||
TagCloseButton,
|
||||
HStack,
|
||||
Tag,
|
||||
Input
|
||||
} from '@chakra-ui/react';
|
||||
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
|
||||
const TagEdit = ({
|
||||
defaultValues,
|
||||
teamsTags,
|
||||
setSelectedTags
|
||||
}: {
|
||||
defaultValues: [];
|
||||
teamsTags: Array<TeamTagsSchema>;
|
||||
setSelectedTags: (item: Array<string>) => void;
|
||||
}) => {
|
||||
const [teamTagsOptions, setTeamTagsOptions] = useState(teamsTags);
|
||||
const setSelectTeamsTags = (item: any) => {
|
||||
setSelectedTags(item);
|
||||
};
|
||||
useMemo(() => {
|
||||
setTeamTagsOptions(teamsTags);
|
||||
}, [teamsTags]);
|
||||
return (
|
||||
<>
|
||||
<Menu closeOnSelect={false}>
|
||||
<MenuButton className="menu-btn" maxHeight={'250'} minWidth={'80%'}>
|
||||
<HStack
|
||||
style={{
|
||||
border: 'solid 2px #f3f3f3',
|
||||
borderRadius: '5px',
|
||||
padding: '3px',
|
||||
|
||||
flexWrap: 'wrap',
|
||||
minHeight: '40px'
|
||||
}}
|
||||
>
|
||||
{teamsTags.map((item: TeamTagsSchema, index: number) => {
|
||||
const key: string = item?.key;
|
||||
if (defaultValues.indexOf(key as never) > -1) {
|
||||
return (
|
||||
<Tag
|
||||
key={index}
|
||||
size={'md'}
|
||||
colorScheme="red"
|
||||
// maxWidth={"100px"}
|
||||
borderRadius="full"
|
||||
>
|
||||
<TagLabel> {item.label}</TagLabel>
|
||||
<TagCloseButton />
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</HStack>
|
||||
</MenuButton>
|
||||
<MenuList style={{ height: '300px', overflow: 'scroll' }}>
|
||||
<Input
|
||||
style={{ border: 'none', borderBottom: 'solid 1px #f6f6f6' }}
|
||||
placeholder="pleace "
|
||||
onChange={(e: any) => {
|
||||
// 对用户输入的搜索文本进行小写转换,以实现不区分大小写的搜索
|
||||
const searchLower: string = e?.nativeEvent?.data || '';
|
||||
// 使用filter方法来过滤列表,只返回包含搜索文本的项
|
||||
const resultList = teamsTags.filter((item) => {
|
||||
const searchValue = item.label || '';
|
||||
// 对列表中的每一项也进行小写转换
|
||||
return searchValue.includes(searchLower);
|
||||
});
|
||||
!searchLower ? setTeamTagsOptions(teamsTags) : setTeamTagsOptions(resultList);
|
||||
}}
|
||||
/>
|
||||
<MenuOptionGroup
|
||||
defaultValue={defaultValues}
|
||||
type="checkbox"
|
||||
style={{ height: '300px', overflow: 'scroll' }}
|
||||
onChange={(e) => {
|
||||
setSelectTeamsTags(e);
|
||||
}}
|
||||
>
|
||||
{teamTagsOptions.map((item, index) => {
|
||||
return (
|
||||
<MenuItemOption key={index} value={item.key}>
|
||||
{item?.label}
|
||||
</MenuItemOption>
|
||||
);
|
||||
})}
|
||||
</MenuOptionGroup>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagEdit;
|
||||
@@ -27,8 +27,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import SelectAiModel from '@/components/Select/SelectAiModel';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
export type DatasetParamsProps = {
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
@@ -63,8 +61,6 @@ const DatasetParamsModal = ({
|
||||
}: DatasetParamsProps & { onClose: () => void; onSuccess: (e: DatasetParamsProps) => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const { reRankModelList, llmModelList } = useSystemStore();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [currentTabType, setCurrentTabType] = useState(SearchSettingTabEnum.searchMode);
|
||||
@@ -75,7 +71,7 @@ const DatasetParamsModal = ({
|
||||
limit,
|
||||
similarity,
|
||||
searchMode,
|
||||
usingReRank: !!usingReRank && !!teamPlanStatus?.standardConstants?.permissionReRank,
|
||||
usingReRank,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
|
||||
datasetSearchExtensionBg
|
||||
@@ -109,10 +105,6 @@ const DatasetParamsModal = ({
|
||||
return true;
|
||||
}, [getValues, similarity]);
|
||||
|
||||
const showReRank = useMemo(() => {
|
||||
return usingReRank !== undefined && reRankModelList.length > 0;
|
||||
}, [reRankModelList.length, usingReRank]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
@@ -156,7 +148,7 @@ const DatasetParamsModal = ({
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
{showReRank && (
|
||||
{usingReRank !== undefined && reRankModelList.length > 0 && (
|
||||
<>
|
||||
<Divider my={4} />
|
||||
<Flex
|
||||
@@ -176,12 +168,6 @@ const DatasetParamsModal = ({
|
||||
}
|
||||
: {})}
|
||||
onClick={(e) => {
|
||||
if (!teamPlanStatus?.standardConstants?.permissionReRank) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('support.team.limit.No permission rerank')
|
||||
});
|
||||
}
|
||||
setValue('usingReRank', !getValues('usingReRank'));
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
@@ -287,7 +273,7 @@ const DatasetParamsModal = ({
|
||||
{currentTabType === SearchSettingTabEnum.queryExtension && (
|
||||
<Box>
|
||||
<Box fontSize={'xs'} color={'myGray.500'}>
|
||||
{t('core.dataset.Query extension intro')}
|
||||
{t('core.module.template.Query extension intro')}
|
||||
</Box>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</Box>
|
||||
|
||||
@@ -46,7 +46,11 @@ const RenderList: {
|
||||
Component: dynamic(() => import('./templates/AiSetting'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.selectLLMModel],
|
||||
types: [
|
||||
FlowNodeInputTypeEnum.selectChatModel,
|
||||
FlowNodeInputTypeEnum.selectCQModel,
|
||||
FlowNodeInputTypeEnum.selectExtractModel
|
||||
],
|
||||
Component: dynamic(() => import('./templates/SelectAiModel'))
|
||||
},
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple,
|
||||
[FlowNodeTypeEnum.queryExtension]: NodeSimple
|
||||
[FlowNodeTypeEnum.cfr]: NodeSimple
|
||||
};
|
||||
const edgeTypes = {
|
||||
[EDGE_TYPE]: ButtonEdge
|
||||
|
||||
@@ -46,7 +46,7 @@ type EditProps = EditApiKeyProps & { _id?: string };
|
||||
const defaultEditData: EditProps = {
|
||||
name: '',
|
||||
limit: {
|
||||
maxUsagePoints: -1
|
||||
credit: -1
|
||||
}
|
||||
};
|
||||
|
||||
@@ -153,16 +153,16 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{apiKeys.map(({ _id, name, usagePoints, limit, apiKey, createTime, lastUsedTime }) => (
|
||||
{apiKeys.map(({ _id, name, usage, limit, apiKey, createTime, lastUsedTime }) => (
|
||||
<Tr key={_id}>
|
||||
<Td>{name}</Td>
|
||||
<Td>{apiKey}</Td>
|
||||
<Td>{Math.round(usagePoints)}</Td>
|
||||
<Td>{usage}</Td>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Td>
|
||||
{limit?.maxUsagePoints && limit?.maxUsagePoints > -1
|
||||
? `${limit?.maxUsagePoints}`
|
||||
{limit?.credit && limit?.credit > -1
|
||||
? `${limit?.credit}`
|
||||
: t('common.Unlimited')}
|
||||
</Td>
|
||||
<Td whiteSpace={'pre-wrap'}>
|
||||
@@ -334,15 +334,15 @@ function EditKeyModal({
|
||||
<>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('support.outlink.Max usage points')}:
|
||||
<MyTooltip label={t('support.outlink.Max usage points tip')}>
|
||||
{t('common.Max credit')}:
|
||||
<MyTooltip label={t('common.Max credit tips' || '')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Input
|
||||
{...register('limit.maxUsagePoints', {
|
||||
{...register('limit.credit', {
|
||||
min: -1,
|
||||
max: 10000000,
|
||||
max: 1000,
|
||||
valueAsNumber: true,
|
||||
required: true
|
||||
})}
|
||||
|
||||
@@ -2,16 +2,13 @@ import React, { useMemo, useState } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { DragHandleIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
getTeamList,
|
||||
getTeamMembers,
|
||||
putSwitchTeam,
|
||||
putUpdateMember,
|
||||
delRemoveMember,
|
||||
delLeaveTeam,
|
||||
getTeamsTags,
|
||||
insertTeamsTags
|
||||
delLeaveTeam
|
||||
} from '@/web/support/user/team/api';
|
||||
import {
|
||||
Box,
|
||||
@@ -23,15 +20,12 @@ import {
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Tag,
|
||||
Td,
|
||||
TableContainer,
|
||||
useTheme,
|
||||
useDisclosure,
|
||||
MenuButton,
|
||||
HStack
|
||||
MenuButton
|
||||
} from '@chakra-ui/react';
|
||||
import { SpinnerIcon } from '@chakra-ui/icons';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
@@ -52,14 +46,12 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
const EditModal = dynamic(() => import('./EditModal'));
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
const TeamTagsAsync = dynamic(() => import('../TeamTagsAsync'));
|
||||
|
||||
const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const { toast } = useToast();
|
||||
const [teamsTags, setTeamTags] = useState<any>();
|
||||
|
||||
const { ConfirmModal: ConfirmRemoveMemberModal, openConfirm: openRemoveMember } = useConfirm();
|
||||
const { ConfirmModal: ConfirmLeaveTeamModal, openConfirm: openLeaveConfirm } = useConfirm({
|
||||
@@ -69,11 +61,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
const [editTeamData, setEditTeamData] = useState<FormDataType>();
|
||||
const { isOpen: isOpenInvite, onOpen: onOpenInvite, onClose: onCloseInvite } = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenTeamTagsAsync,
|
||||
onOpen: onOpenTeamTagsAsync,
|
||||
onClose: onCloseTeamTagsAsync
|
||||
} = useDisclosure();
|
||||
|
||||
const {
|
||||
data: myTeams = [],
|
||||
@@ -89,8 +76,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
mutationFn: async (teamId: string) => {
|
||||
const token = await putSwitchTeam(teamId);
|
||||
token && setToken(token);
|
||||
// get team tags
|
||||
await getTeamsTags(teamId);
|
||||
return initUserInfo();
|
||||
},
|
||||
errorToast: t('user.team.Switch Team Failed')
|
||||
@@ -101,11 +86,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
['getMembers', userInfo?.team?.teamId],
|
||||
() => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
// get team tags
|
||||
getTeamsTags(userInfo.team.teamId).then((res: any) => {
|
||||
setTeamTags(res);
|
||||
});
|
||||
|
||||
return getTeamMembers(userInfo.team.teamId);
|
||||
}
|
||||
);
|
||||
@@ -128,9 +108,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
mutationFn: async (teamId?: string) => {
|
||||
if (!teamId) return;
|
||||
// change to personal team
|
||||
// get members
|
||||
await onSwitchTeam(defaultTeam.teamId);
|
||||
|
||||
return delLeaveTeam(teamId);
|
||||
},
|
||||
onSuccess() {
|
||||
@@ -206,7 +184,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
bg: 'myGray.100'
|
||||
}
|
||||
})}
|
||||
onClick={() => onSwitchTeam(team.teamId)}
|
||||
>
|
||||
<Avatar src={team.avatar} w={['18px', '22px']} />
|
||||
<Box
|
||||
@@ -219,17 +196,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
: {})}
|
||||
>
|
||||
{team.teamName}
|
||||
{/* {userInfo?.team?.teamId === team.teamId && (
|
||||
<HStack spacing={1}>
|
||||
{teamsTags.slice(0, 3).map((item: any, index) => {
|
||||
return (
|
||||
<Tag key={index} size={'sm'} variant="outline" colorScheme="blue">
|
||||
{item.label}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
)} */}
|
||||
</Box>
|
||||
{userInfo?.team?.teamId === team.teamId ? (
|
||||
<MyIcon name={'common/tickFill'} w={'16px'} color={'primary.500'} />
|
||||
@@ -263,7 +229,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
borderBottomColor={'myGray.100'}
|
||||
mb={3}
|
||||
>
|
||||
<Box fontSize={['lg', 'xl']} fontWeight={'bold'} alignItems={'center'}>
|
||||
<Box fontSize={['lg', 'xl']} fontWeight={'bold'}>
|
||||
{userInfo.team.teamName}
|
||||
</Box>
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
||||
@@ -313,27 +279,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
{t('user.team.Invite Member')}
|
||||
</Button>
|
||||
)}
|
||||
{userInfo.team.role === TeamMemberRoleEnum.owner && (
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<DragHandleIcon w={'14px'} color={'primary.500'} />}
|
||||
onClick={() => {
|
||||
if (userInfo.team.maxSize <= members.length) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('user.team.Team Tags Async', { max: userInfo.team.maxSize })
|
||||
});
|
||||
} else {
|
||||
onOpenTeamTagsAsync();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('user.team.Team Tags Async')}
|
||||
</Button>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
{userInfo.team.role !== TeamMemberRoleEnum.owner && (
|
||||
<Button
|
||||
@@ -490,13 +435,6 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
onSuccess={refetchMembers}
|
||||
/>
|
||||
)}
|
||||
{isOpenTeamTagsAsync && (
|
||||
<TeamTagsAsync
|
||||
teamInfo={teamsTags?.tagsUrl}
|
||||
teamsTags={teamsTags?.list || []}
|
||||
onClose={onCloseTeamTagsAsync}
|
||||
/>
|
||||
)}
|
||||
<ConfirmRemoveMemberModal />
|
||||
<ConfirmLeaveTeamModal />
|
||||
</>
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
ModalBody,
|
||||
Tag,
|
||||
ModalFooter,
|
||||
Input,
|
||||
HStack,
|
||||
Avatar
|
||||
} from '@chakra-ui/react';
|
||||
import { AttachmentIcon, CopyIcon, DragHandleIcon } from '@chakra-ui/icons';
|
||||
import { putUpdateTeamTags, updateTags } from '@/web/support/user/team/api';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { RepeatIcon } from '@chakra-ui/icons';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
|
||||
const TeamTagsAsync = ({
|
||||
teamsTags,
|
||||
teamInfo,
|
||||
onClose
|
||||
}: {
|
||||
teamsTags: Array<TeamTagsSchema>;
|
||||
teamInfo: any;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [_teamsTags, setTeamTags] = useState<Array<TeamTagsSchema>>(teamsTags);
|
||||
|
||||
const { register, setValue, getValues, handleSubmit } = useForm<any>({
|
||||
defaultValues: { ...teamInfo }
|
||||
});
|
||||
const { copyData } = useCopyData();
|
||||
const baseUrl = global.feConfigs?.customSharePageDomain || location?.origin;
|
||||
const linkUrl = `${baseUrl}/chat/team?teamId=${teamInfo?._id}${
|
||||
getValues('showHistory') ? '' : '&showHistory=0'
|
||||
}`;
|
||||
|
||||
// tags Async
|
||||
const { mutate: onclickAsync, isLoading: creating } = useRequest({
|
||||
mutationFn: async (data: any) => {
|
||||
return putUpdateTeamTags({ tagsUrl: data.tagsUrl, teamId: teamInfo?._id });
|
||||
},
|
||||
onSuccess(id: string) {
|
||||
onClose();
|
||||
},
|
||||
successToast: t('user.team.Team Tags Async Success'),
|
||||
errorToast: t('common.Create Failed')
|
||||
});
|
||||
const asyncTags = async () => {
|
||||
console.log('getValues', getValues());
|
||||
const res: Array<TeamTagsSchema> = await updateTags(teamInfo?._id, getValues().tagsUrl);
|
||||
setTeamTags(res);
|
||||
toast({ status: 'success', title: '团队标签同步成功' });
|
||||
};
|
||||
useEffect(() => {
|
||||
console.log('teamInfo', teamInfo);
|
||||
}, []);
|
||||
|
||||
// 获取
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
maxW={['70vw', '1000px']}
|
||||
w={'100%'}
|
||||
h={'550px'}
|
||||
iconSrc="/imgs/modal/team.svg"
|
||||
isCentered
|
||||
bg={'white'}
|
||||
overflow={'hidden'}
|
||||
title={
|
||||
<Box>
|
||||
<Box>{teamInfo?.name}</Box>
|
||||
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
|
||||
{'填写标签同步链接,点击同步按钮即可同步'}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<ModalBody style={{ padding: '10rpx' }}>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box mb={2} fontWeight="semibold">
|
||||
{t('同步链接')}
|
||||
</Box>
|
||||
<Input
|
||||
flex={1}
|
||||
ml={4}
|
||||
autoFocus
|
||||
bg={'myWhite.600'}
|
||||
placeholder="请输入同步标签"
|
||||
{...register('tagsUrl', {
|
||||
required: t('core.app.error.App name can not be empty')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box mb={2} fontWeight="semibold">
|
||||
{t('分享链接')}
|
||||
</Box>
|
||||
{/* code */}
|
||||
<Box ml={4} borderRadius={'md'} overflow={'hidden'}>
|
||||
<Flex>
|
||||
<Box whiteSpace={'pre'} p={3} overflowX={'auto'} bg={'myWhite.600'} color="blue">
|
||||
{linkUrl}
|
||||
</Box>
|
||||
<MyIcon
|
||||
name={'copy'}
|
||||
w={'16px'}
|
||||
p={3}
|
||||
bg={'primary.500'}
|
||||
color={'myWhite.600'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'primary.400' }}
|
||||
onClick={() => {
|
||||
copyData(linkUrl);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box mb={2} fontWeight="semibold">
|
||||
{t('标签列表')}
|
||||
</Box>
|
||||
<HStack
|
||||
ml={4}
|
||||
maxHeight={'250'}
|
||||
bg={'myWhite.600'}
|
||||
style={{
|
||||
border: 'solid 2px #f3f3f377',
|
||||
borderRadius: '5px',
|
||||
padding: '10px',
|
||||
maxWidth: '70%',
|
||||
flexWrap: 'wrap',
|
||||
overflow: 'scroll'
|
||||
}}
|
||||
spacing={1}
|
||||
>
|
||||
{_teamsTags.map((item, index) => {
|
||||
return (
|
||||
<Tag key={index} mt={2} size={'md'} colorScheme="red" borderRadius="full">
|
||||
<Avatar
|
||||
src="https://bit.ly/sage-adeb"
|
||||
size="xs"
|
||||
name={item.label}
|
||||
ml={-2}
|
||||
mr={2}
|
||||
/>
|
||||
{item.label}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
<Button ml={4} size="md" leftIcon={<RepeatIcon />} onClick={asyncTags}>
|
||||
立即同步
|
||||
</Button>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter mb={2}>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button isLoading={creating} onClick={handleSubmit((data) => onclickAsync(data))}>
|
||||
{t('user.team.Tags Async')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TeamTagsAsync);
|
||||
97
projects/app/src/components/support/wallet/Price.tsx
Normal file
97
projects/app/src/components/support/wallet/Price.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
import { Box, CloseButton } from '@chakra-ui/react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import Markdown from '@/components/Markdown';
|
||||
|
||||
const Price = ({ onClose }: { onClose: () => void }) => {
|
||||
const { llmModelList, vectorModelList, audioSpeechModelList, whisperModel } = useSystemStore();
|
||||
|
||||
const list = [
|
||||
{
|
||||
title: 'AI语言模型',
|
||||
describe: '',
|
||||
md: `
|
||||
| 模型 | 输入价格(¥) | 输出价格(¥) |
|
||||
| --- | --- | --- |
|
||||
${llmModelList
|
||||
?.map((item) => `| ${item.name} | ${item.inputPrice}/1k tokens | ${item.outputPrice}/1k tokens |`)
|
||||
.join('\n')}`
|
||||
},
|
||||
{
|
||||
title: '索引模型(文档训练 & 文档检索)',
|
||||
describe: '',
|
||||
md: `
|
||||
| 模型 | 价格(¥) |
|
||||
| --- | --- |
|
||||
${vectorModelList?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 |`).join('\n')}
|
||||
`
|
||||
},
|
||||
{
|
||||
title: '语音播放',
|
||||
describe: '',
|
||||
md: `
|
||||
| 模型 | 价格(¥) |
|
||||
| --- | --- |
|
||||
${audioSpeechModelList
|
||||
?.map((item) => `| ${item.name} | ${item.inputPrice}/1k 字符 | - |`)
|
||||
.join('\n')}`
|
||||
},
|
||||
...(whisperModel
|
||||
? [
|
||||
{
|
||||
title: '语音输入',
|
||||
describe: '',
|
||||
md: `
|
||||
| 模型 | 价格(¥) |
|
||||
| --- | --- |
|
||||
| ${whisperModel.name} | ${whisperModel.inputPrice}/分钟 | - |`
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<Box position={'fixed'} top={0} right={0} bottom={0} left={0} zIndex={99999} bg={'white'}>
|
||||
<CloseButton
|
||||
position={'absolute'}
|
||||
top={'10px'}
|
||||
right={'20px'}
|
||||
bg={'myGray.200'}
|
||||
w={'30px'}
|
||||
h={'30px'}
|
||||
borderRadius={'50%'}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<Box overflow={'overlay'} h={'100%'}>
|
||||
<Box py={[0, 10]} px={5} mx={'auto'} maxW={'1200px'}>
|
||||
{list.map((item) => (
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
key={item.title}
|
||||
w={'100%'}
|
||||
mb={4}
|
||||
pb={6}
|
||||
_notLast={{
|
||||
borderBottom: '1px',
|
||||
borderBottomColor: 'borderColor.high'
|
||||
}}
|
||||
>
|
||||
<Box fontSize={'xl'} fontWeight={'bold'} mb={1} flex={'1 0 0'}>
|
||||
{item.title}
|
||||
</Box>
|
||||
<Box w={['100%', '410px']}>
|
||||
<Markdown source={item.md}></Markdown>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>,
|
||||
// @ts-ignore
|
||||
document.querySelector('body')
|
||||
);
|
||||
};
|
||||
|
||||
export default Price;
|
||||
@@ -1,84 +0,0 @@
|
||||
import MyModal from '@/components/MyModal';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { checkBalancePayResult } from '@/web/support/wallet/bill/api';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
export type QRPayProps = {
|
||||
readPrice: number;
|
||||
codeUrl: string;
|
||||
billId: string;
|
||||
};
|
||||
|
||||
const QRCodePayModal = ({
|
||||
readPrice,
|
||||
codeUrl,
|
||||
billId,
|
||||
onSuccess
|
||||
}: QRPayProps & { onSuccess?: () => any }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const dom = document.getElementById('payQRCode');
|
||||
|
||||
useEffect(() => {
|
||||
if (dom && window.QRCode) {
|
||||
new window.QRCode(dom, {
|
||||
text: codeUrl,
|
||||
width: 128,
|
||||
height: 128,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: window.QRCode.CorrectLevel.H
|
||||
});
|
||||
}
|
||||
}, [dom]);
|
||||
|
||||
useQuery(
|
||||
[billId],
|
||||
() => {
|
||||
if (!billId) return null;
|
||||
return checkBalancePayResult(billId);
|
||||
},
|
||||
{
|
||||
enabled: !!billId,
|
||||
refetchInterval: 3000,
|
||||
onSuccess: async (res) => {
|
||||
if (!res) return;
|
||||
|
||||
try {
|
||||
await onSuccess?.();
|
||||
toast({
|
||||
title: res,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: getErrText(error),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
router.reload();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal isOpen title={t('user.Pay')} iconSrc="/imgs/modal/pay.svg">
|
||||
<ModalBody textAlign={'center'}>
|
||||
<Box mb={3}>请微信扫码支付: {readPrice}元,请勿关闭页面</Box>
|
||||
<Box id={'payQRCode'} display={'inline-block'} h={'128px'}></Box>
|
||||
</ModalBody>
|
||||
<ModalFooter />
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(QRCodePayModal);
|
||||
@@ -1,116 +0,0 @@
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import React, { useMemo } from 'react';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { Box, Flex, Grid } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const StandardPlanContentList = ({
|
||||
level,
|
||||
mode
|
||||
}: {
|
||||
level: `${StandardSubLevelEnum}`;
|
||||
mode: `${SubModeEnum}`;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { subPlans } = useSystemStore();
|
||||
|
||||
const planContent = useMemo(() => {
|
||||
const plan = subPlans?.standard?.[level];
|
||||
if (!plan) return;
|
||||
return {
|
||||
price: plan.price * (mode === SubModeEnum.month ? 1 : 10),
|
||||
level: level as `${StandardSubLevelEnum}`,
|
||||
...standardSubLevelMap[level as `${StandardSubLevelEnum}`],
|
||||
maxTeamMember: plan.maxTeamMember,
|
||||
maxAppAmount: plan.maxAppAmount,
|
||||
maxDatasetAmount: plan.maxDatasetAmount,
|
||||
chatHistoryStoreDuration: plan.chatHistoryStoreDuration,
|
||||
maxDatasetSize: plan.maxDatasetSize,
|
||||
permissionCustomApiKey: plan.permissionCustomApiKey,
|
||||
permissionCustomCopyright: plan.permissionCustomCopyright,
|
||||
trainingWeight: plan.trainingWeight,
|
||||
permissionReRank: plan.permissionReRank,
|
||||
totalPoints: plan.totalPoints * (mode === SubModeEnum.month ? 1 : 12),
|
||||
permissionWebsiteSync: plan.permissionWebsiteSync
|
||||
};
|
||||
}, [subPlans?.standard, level, mode]);
|
||||
|
||||
return planContent ? (
|
||||
<Grid gap={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max members', {
|
||||
amount: planContent.maxTeamMember
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max app', {
|
||||
amount: planContent.maxAppAmount
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max dataset', {
|
||||
amount: planContent.maxDatasetAmount
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.History store', {
|
||||
amount: planContent.chatHistoryStoreDuration
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box fontWeight={'bold'}>
|
||||
{t('support.wallet.subscription.function.Max dataset size', {
|
||||
amount: planContent.maxDatasetSize
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontWeight={'bold'}>
|
||||
{t('support.wallet.subscription.function.Points', {
|
||||
amount: planContent.totalPoints
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.Training weight', {
|
||||
weight: planContent.trainingWeight
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
{!!planContent.permissionReRank && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>检索结果重排</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!planContent.permissionWebsiteSync && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>Web站点同步</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Grid>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default StandardPlanContentList;
|
||||
240
projects/app/src/components/support/wallet/SubDatasetModal.tsx
Normal file
240
projects/app/src/components/support/wallet/SubDatasetModal.tsx
Normal file
@@ -0,0 +1,240 @@
|
||||
import React, { useState } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
ModalBody,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper,
|
||||
ModalFooter,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
getTeamDatasetValidSub,
|
||||
posCheckTeamDatasetSizeSub,
|
||||
postUpdateTeamDatasetSizeSub,
|
||||
putTeamDatasetSubStatus
|
||||
} from '@/web/support/wallet/sub/api';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import MySelect from '@/components/Select';
|
||||
import {
|
||||
SubStatusEnum,
|
||||
SubTypeEnum,
|
||||
subSelectMap
|
||||
} from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { SubDatasetSizePreviewCheckResponse } from '@fastgpt/global/support/wallet/sub/api.d';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
const SubDatasetModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { subPlans } = useSystemStore();
|
||||
const datasetStorePrice = subPlans?.extraDatasetSize?.price || 0;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
const { userInfo } = useUserStore();
|
||||
const [datasetSize, setDatasetSize] = useState(0);
|
||||
const [isRenew, setIsRenew] = useState('false');
|
||||
|
||||
const { data: teamSubPlan } = useQuery(['getTeamDatasetValidSub'], getTeamDatasetValidSub, {
|
||||
onSuccess(res) {
|
||||
setIsRenew(res?.extraDatasetSize?.status === SubStatusEnum.active ? 'true' : 'false');
|
||||
setDatasetSize((res?.extraDatasetSize?.nextExtraDatasetSize || 0) / 1000);
|
||||
}
|
||||
});
|
||||
|
||||
const { mutate: onClickUpdateSub, isLoading: isPaying } = useRequest({
|
||||
mutationFn: () => postUpdateTeamDatasetSizeSub({ size: datasetSize }),
|
||||
onSuccess() {
|
||||
setTimeout(() => {
|
||||
router.reload();
|
||||
}, 100);
|
||||
},
|
||||
successToast: t('common.Update success'),
|
||||
errorToast: t('common.error.Update error')
|
||||
});
|
||||
|
||||
const { mutate: onClickPreviewCheck, isLoading: isFetchingPreviewCheck } = useRequest({
|
||||
mutationFn: () =>
|
||||
posCheckTeamDatasetSizeSub({
|
||||
size: datasetSize
|
||||
}),
|
||||
onSuccess(res: SubDatasetSizePreviewCheckResponse) {
|
||||
if (!res.payForNewSub) {
|
||||
onClickUpdateSub('');
|
||||
return;
|
||||
} else {
|
||||
openConfirm(
|
||||
() => {
|
||||
if (!res.balanceEnough) return;
|
||||
onClickUpdateSub('');
|
||||
},
|
||||
undefined,
|
||||
<Box>
|
||||
<Flex>
|
||||
<Box flex={'0 0 100px'}>当前额外容量:</Box>
|
||||
<Box>{teamSubPlan?.extraDatasetSize?.currentExtraDatasetSize || 0}条</Box>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Box flex={'0 0 100px'}>新的额外容量:</Box>
|
||||
<Box>{res.newSubSize}条</Box>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Box flex={'0 0 100px'}>新套餐价格:</Box>
|
||||
<Box>{formatStorePrice2Read(res.newPlanPrice)}元</Box>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Box flex={'0 0 100px'}>本次需支付:</Box>
|
||||
<Box>{formatStorePrice2Read(res.payPrice)}元</Box>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Box flex={'0 0 100px'}>有效时长:</Box>
|
||||
<Box>30天</Box>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Box flex={'0 0 100px'}>账号余额:</Box>
|
||||
<Box>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}元</Box>
|
||||
</Flex>
|
||||
{!res.balanceEnough && (
|
||||
<Box mt={1} color={'red.600'}>
|
||||
账号余额不足,请先充值余额再购买额外容量。
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)();
|
||||
}
|
||||
},
|
||||
errorToast: t('common.error.Update error')
|
||||
});
|
||||
const { mutate: onUpdateStatus } = useRequest({
|
||||
mutationFn: (e: 'true' | 'false') => {
|
||||
setIsRenew(e);
|
||||
return putTeamDatasetSubStatus({
|
||||
status: subSelectMap[e],
|
||||
type: SubTypeEnum.extraDatasetSize
|
||||
});
|
||||
},
|
||||
successToast: t('common.Update success'),
|
||||
errorToast: t('common.error.Update error')
|
||||
});
|
||||
|
||||
const isLoading = isPaying || isFetchingPreviewCheck;
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="/imgs/module/db.png"
|
||||
title={t('support.wallet.subscription.Dataset store')}
|
||||
>
|
||||
<ModalBody>
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
{t('support.user.Price')}
|
||||
<MyTooltip label={t('support.wallet.subscription.Dataset store price tip')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Markdown
|
||||
source={`
|
||||
| 套餐知识库容量 | ${teamSubPlan?.standardMaxDatasetSize || Infinity}条 |
|
||||
| --- | --- |
|
||||
| 额外知识库 | ${datasetStorePrice}元/1000条/月 |
|
||||
`}
|
||||
/>
|
||||
</>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Current dataset store')}: </Box>
|
||||
<Box ml={2} fontWeight={'bold'} flex={1}>
|
||||
{teamSubPlan?.extraDatasetSize?.currentExtraDatasetSize || 0}
|
||||
{t('core.dataset.data.unit')}
|
||||
</Box>
|
||||
</Flex>
|
||||
{teamSubPlan?.extraDatasetSize?.nextExtraDatasetSize !== undefined && (
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Next sub dataset size')}: </Box>
|
||||
<Box ml={2} fontWeight={'bold'} flex={1}>
|
||||
{teamSubPlan?.extraDatasetSize?.nextExtraDatasetSize || 0}
|
||||
{t('core.dataset.data.unit')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!teamSubPlan?.extraDatasetSize?.startTime && (
|
||||
<Flex mt={3}>
|
||||
<Box flex={'0 0 120px'}>订阅开始时间: </Box>
|
||||
<Box ml={2}>{formatTime2YMDHM(teamSubPlan?.extraDatasetSize?.startTime)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!teamSubPlan?.extraDatasetSize?.expiredTime && (
|
||||
<Flex mt={3}>
|
||||
<Box flex={'0 0 120px'}>订阅到期时间: </Box>
|
||||
<Box ml={2}>{formatTime2YMDHM(teamSubPlan?.extraDatasetSize?.expiredTime)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box flex={'0 0 120px'}>是否自动续费: </Box>
|
||||
<MySelect
|
||||
ml={2}
|
||||
value={isRenew}
|
||||
size={'sm'}
|
||||
w={'150px'}
|
||||
list={[
|
||||
{ label: '自动续费', value: 'true' },
|
||||
{ label: '不自动续费', value: 'false' }
|
||||
]}
|
||||
onchange={onUpdateStatus}
|
||||
/>
|
||||
</Flex>
|
||||
<Box mt={4}>
|
||||
<Box>{t('support.wallet.subscription.Update extra dataset size')}</Box>
|
||||
<Flex alignItems={'center'} mt={1}>
|
||||
<NumberInput
|
||||
flex={1}
|
||||
min={0}
|
||||
max={10000}
|
||||
step={1}
|
||||
value={datasetSize}
|
||||
position={'relative'}
|
||||
onChange={(e) => {
|
||||
setDatasetSize(Number(e));
|
||||
}}
|
||||
>
|
||||
<NumberInputField value={datasetSize} step={1} min={0} max={10000} />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
<Box ml={2}>000{t('core.dataset.data.unit')}</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
{datasetSize * 1000 !== teamSubPlan?.extraDatasetSize?.nextExtraDatasetSize && (
|
||||
<Button ml={3} isLoading={isLoading} onClick={onClickPreviewCheck}>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
|
||||
<ConfirmModal />
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubDatasetModal;
|
||||
@@ -15,8 +15,7 @@ export const defaultApp: AppDetailType = {
|
||||
tmbId: '',
|
||||
permission: 'private',
|
||||
isOwner: false,
|
||||
canWrite: false,
|
||||
teamTags: ['']
|
||||
canWrite: false
|
||||
};
|
||||
|
||||
export const defaultOutLinkForm: OutLinkEditType = {
|
||||
@@ -24,7 +23,7 @@ export const defaultOutLinkForm: OutLinkEditType = {
|
||||
responseDetail: false,
|
||||
limit: {
|
||||
QPM: 100,
|
||||
maxUsagePoints: -1
|
||||
credit: -1
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
7
projects/app/src/global/core/chat/api.d.ts
vendored
7
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -14,12 +14,6 @@ export type InitChatProps = {
|
||||
chatId?: string;
|
||||
loadCustomFeedbacks?: boolean;
|
||||
};
|
||||
/* ---------- chat ----------- */
|
||||
export type chatByTeamProps = {
|
||||
teamId?: string;
|
||||
appId?: string;
|
||||
outLinkUid?: string;
|
||||
};
|
||||
export type InitOutLinkChatProps = {
|
||||
chatId?: string;
|
||||
shareId?: string;
|
||||
@@ -45,7 +39,6 @@ export type InitChatResponse = {
|
||||
/* ---------- history ----------- */
|
||||
export type getHistoriesProps = {
|
||||
appId?: string;
|
||||
authToken?: string;
|
||||
// share chat
|
||||
shareId?: string;
|
||||
outLinkUid?: string; // authToken/uid
|
||||
|
||||
@@ -4,34 +4,42 @@ export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
|
||||
{
|
||||
title: '标准模板',
|
||||
desc: '标准提示词,用于结构不固定的知识库。',
|
||||
value: `{{q}}
|
||||
{{a}}`
|
||||
value: `<data>
|
||||
{{q}}
|
||||
{{a}}
|
||||
</data>`
|
||||
},
|
||||
{
|
||||
title: '问答模板',
|
||||
desc: '适合 QA 问答结构的知识库,可以让AI较为严格的按预设内容回答',
|
||||
value: `<Question>
|
||||
value: `<QA>
|
||||
<问题>
|
||||
{{q}}
|
||||
</Question>
|
||||
<Answer>
|
||||
</问题>
|
||||
<答案>
|
||||
{{a}}
|
||||
</Answer>`
|
||||
</答案>
|
||||
</QA>`
|
||||
},
|
||||
{
|
||||
title: '标准严格模板',
|
||||
desc: '在标准模板基础上,对模型的回答做更严格的要求。',
|
||||
value: `{{q}}
|
||||
{{a}}`
|
||||
value: `<data>
|
||||
{{q}}
|
||||
{{a}}
|
||||
</data>`
|
||||
},
|
||||
{
|
||||
title: '严格问答模板',
|
||||
desc: '在问答模板基础上,对模型的回答做更严格的要求。',
|
||||
value: `<Question>
|
||||
value: `<QA>
|
||||
<问题>
|
||||
{{q}}
|
||||
</Question>
|
||||
<Answer>
|
||||
</问题>
|
||||
<答案>
|
||||
{{a}}
|
||||
</Answer>`
|
||||
</答案>
|
||||
</QA>`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -39,16 +47,14 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
{
|
||||
title: '标准模板',
|
||||
desc: '',
|
||||
value: `使用 <Data></Data> 标记中的内容作为你的知识:
|
||||
value: `使用 <data></data> 标记中的内容作为你的知识:
|
||||
|
||||
<Data>
|
||||
{{quote}}
|
||||
</Data>
|
||||
|
||||
回答要求:
|
||||
- 如果你不清楚答案,你需要澄清。
|
||||
- 避免提及你是从 <Data></Data> 获取的知识。
|
||||
- 保持答案与 <Data></Data> 中描述的一致。
|
||||
- 避免提及你是从 <data></data> 获取的知识。
|
||||
- 保持答案与 <data></data> 中描述的一致。
|
||||
- 使用 Markdown 语法优化回答格式。
|
||||
- 使用与问题相同的语言回答。
|
||||
|
||||
@@ -59,9 +65,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
desc: '',
|
||||
value: `使用 <QA></QA> 标记中的问答对进行回答。
|
||||
|
||||
<QA>
|
||||
{{quote}}
|
||||
</QA>
|
||||
|
||||
回答要求:
|
||||
- 选择其中一个或多个问答对进行回答。
|
||||
@@ -74,20 +78,18 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
{
|
||||
title: '标准严格模板',
|
||||
desc: '',
|
||||
value: `忘记你已有的知识,仅使用 <Data></Data> 标记中的内容作为你的知识:
|
||||
value: `忘记你已有的知识,仅使用 <data></data> 标记中的内容作为你的知识:
|
||||
|
||||
<Data>
|
||||
{{quote}}
|
||||
</Data>
|
||||
|
||||
思考流程:
|
||||
1. 判断问题是否与 <Data></Data> 标记中的内容有关。
|
||||
1. 判断问题是否与 <data></data> 标记中的内容有关。
|
||||
2. 如果有关,你按下面的要求回答。
|
||||
3. 如果无关,你直接拒绝回答本次问题。
|
||||
|
||||
回答要求:
|
||||
- 避免提及你是从 <Data></Data> 获取的知识。
|
||||
- 保持答案与 <Data></Data> 中描述的一致。
|
||||
- 避免提及你是从 <data></data> 获取的知识。
|
||||
- 保持答案与 <data></data> 中描述的一致。
|
||||
- 使用 Markdown 语法优化回答格式。
|
||||
- 使用与问题相同的语言回答。
|
||||
|
||||
@@ -98,9 +100,7 @@ export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
desc: '',
|
||||
value: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
|
||||
|
||||
<QA>
|
||||
{{quote}}
|
||||
</QA>}
|
||||
|
||||
思考流程:
|
||||
1. 判断问题是否与 <QA></QA> 标记中的内容有关。
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
function Error() {
|
||||
const router = useRouter();
|
||||
@@ -25,12 +24,11 @@ function Error() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box whiteSpace={'pre-wrap'}>
|
||||
{`出现未捕获的异常。
|
||||
1. 私有部署用户,90%由于配置文件不正确导致。
|
||||
2. 部分系统不兼容相关API。大部分是苹果的safari 浏览器导致,可以尝试更换 chrome。
|
||||
3. 请关闭浏览器翻译功能,部分翻译导致页面崩溃。`}
|
||||
</Box>
|
||||
<p>
|
||||
部分系统不兼容,导致页面崩溃。如果可以,请联系作者,反馈下具体操作和页面。 大部分是 苹果 的
|
||||
safari 浏览器导致,可以尝试更换 chrome
|
||||
浏览器。或者是因为开了中文翻译导致,请检查并关闭中文翻译。
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
160
projects/app/src/pages/account/components/BillDetail.tsx
Normal file
160
projects/app/src/pages/account/components/BillDetail.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
ModalBody,
|
||||
Flex,
|
||||
Box,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer
|
||||
} from '@chakra-ui/react';
|
||||
import { BillItemType } from '@fastgpt/global/support/wallet/bill/type.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const BillDetail = ({ bill, onClose }: { bill: BillItemType; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const filterBillList = useMemo(
|
||||
() => bill.list.filter((item) => item && item.moduleName),
|
||||
[bill.list]
|
||||
);
|
||||
|
||||
const {
|
||||
hasModel,
|
||||
hasTokens,
|
||||
hasInputTokens,
|
||||
hasOutputTokens,
|
||||
hasCharsLen,
|
||||
hasDuration,
|
||||
hasDataLen,
|
||||
hasDatasetSize
|
||||
} = useMemo(() => {
|
||||
let hasModel = false;
|
||||
let hasTokens = false;
|
||||
let hasInputTokens = false;
|
||||
let hasOutputTokens = false;
|
||||
let hasCharsLen = false;
|
||||
let hasDuration = false;
|
||||
let hasDataLen = false;
|
||||
let hasDatasetSize = false;
|
||||
|
||||
bill.list.forEach((item) => {
|
||||
if (item.model !== undefined) {
|
||||
hasModel = true;
|
||||
}
|
||||
if (typeof item.tokenLen === 'number') {
|
||||
hasTokens = true;
|
||||
}
|
||||
if (typeof item.inputTokens === 'number') {
|
||||
hasInputTokens = true;
|
||||
}
|
||||
if (typeof item.outputTokens === 'number') {
|
||||
hasOutputTokens = true;
|
||||
}
|
||||
if (typeof item.charsLength === 'number') {
|
||||
hasCharsLen = true;
|
||||
}
|
||||
if (typeof item.duration === 'number') {
|
||||
hasDuration = true;
|
||||
}
|
||||
if (typeof item.datasetSize === 'number') {
|
||||
hasDatasetSize = true;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
hasModel,
|
||||
hasTokens,
|
||||
hasInputTokens,
|
||||
hasOutputTokens,
|
||||
hasCharsLen,
|
||||
hasDuration,
|
||||
hasDataLen,
|
||||
hasDatasetSize
|
||||
};
|
||||
}, [bill.list]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('user.Bill Detail')}
|
||||
maxW={['90vw', '700px']}
|
||||
>
|
||||
<ModalBody>
|
||||
{/* <Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.bill username')}:</Box>
|
||||
<Box>{t(bill.memberName)}</Box>
|
||||
</Flex> */}
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.Number')}:</Box>
|
||||
<Box>{bill.id}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.Time')}:</Box>
|
||||
<Box>{dayjs(bill.time).format('YYYY/MM/DD HH:mm:ss')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.App name')}:</Box>
|
||||
<Box>{t(bill.appName) || '-'}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.Source')}:</Box>
|
||||
<Box>{t(BillSourceMap[bill.source]?.label)}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('wallet.bill.Total')}:</Box>
|
||||
<Box fontWeight={'bold'}>{bill.total}元</Box>
|
||||
</Flex>
|
||||
<Box pb={4}>
|
||||
<Box flex={'0 0 80px'} mb={1}>
|
||||
{t('wallet.bill.Bill Module')}
|
||||
</Box>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('wallet.bill.Module name')}</Th>
|
||||
{hasModel && <Th>{t('wallet.bill.Ai model')}</Th>}
|
||||
{hasTokens && <Th>{t('wallet.bill.Token Length')}</Th>}
|
||||
{hasInputTokens && <Th>{t('wallet.bill.Input Token Length')}</Th>}
|
||||
{hasOutputTokens && <Th>{t('wallet.bill.Output Token Length')}</Th>}
|
||||
{hasCharsLen && <Th>{t('wallet.bill.Text Length')}</Th>}
|
||||
{hasDuration && <Th>{t('wallet.bill.Duration')}</Th>}
|
||||
{hasDatasetSize && (
|
||||
<Th>{t('support.wallet.subscription.type.extraDatasetSize')}</Th>
|
||||
)}
|
||||
<Th>费用(¥)</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{filterBillList.map((item, i) => (
|
||||
<Tr key={i}>
|
||||
<Td>{t(item.moduleName)}</Td>
|
||||
{hasModel && <Td>{item.model ?? '-'}</Td>}
|
||||
{hasTokens && <Td>{item.tokenLen ?? '-'}</Td>}
|
||||
{hasInputTokens && <Td>{item.inputTokens ?? '-'}</Td>}
|
||||
{hasOutputTokens && <Td>{item.outputTokens ?? '-'}</Td>}
|
||||
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
|
||||
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
|
||||
{hasDatasetSize && <Td>{item.datasetSize ?? '-'}</Td>}
|
||||
<Td>{formatStorePrice2Read(item.amount)}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default BillDetail;
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
@@ -10,38 +9,43 @@ import {
|
||||
TableContainer,
|
||||
Flex,
|
||||
Box,
|
||||
ModalBody
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import { getBills, checkBalancePayResult } from '@/web/support/wallet/bill/api';
|
||||
import type { BillSchemaType } from '@fastgpt/global/support/wallet/bill/type.d';
|
||||
import { BillSourceEnum, BillSourceMap } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { getUserBills } from '@/web/support/wallet/bill/api';
|
||||
import type { BillItemType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { usePagination } from '@/web/common/hooks/usePagination';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
|
||||
import { addDays } from 'date-fns';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import {
|
||||
BillTypeEnum,
|
||||
billPayWayMap,
|
||||
billStatusMap,
|
||||
billTypeMap
|
||||
} from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { usePagination } from '@/web/common/hooks/usePagination';
|
||||
import MyBox from '@/components/common/MyBox';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import Avatar from '@/components/Avatar';
|
||||
const BillDetail = dynamic(() => import('./BillDetail'));
|
||||
|
||||
const BillTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [billType, setBillType] = useState<`${BillTypeEnum}` | ''>('');
|
||||
const [billDetail, setBillDetail] = useState<BillSchemaType>();
|
||||
const { Loading } = useLoading();
|
||||
const [dateRange, setDateRange] = useState<DateRangeType>({
|
||||
from: addDays(new Date(), -7),
|
||||
to: new Date()
|
||||
});
|
||||
const [billSource, setBillSource] = useState<`${BillSourceEnum}` | ''>('');
|
||||
const { isPc } = useSystemStore();
|
||||
const { userInfo } = useUserStore();
|
||||
const [billDetail, setBillDetail] = useState<BillItemType>();
|
||||
|
||||
const billTypeList = useMemo(
|
||||
const sourceList = useMemo(
|
||||
() => [
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(billTypeMap).map(([key, value]) => ({
|
||||
...Object.entries(BillSourceMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
@@ -49,193 +53,134 @@ const BillTable = () => {
|
||||
[t]
|
||||
);
|
||||
|
||||
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
|
||||
const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
return getTeamMembers(userInfo.team.teamId);
|
||||
});
|
||||
const tmbList = useMemo(
|
||||
() =>
|
||||
members.map((item) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={'16px'} mr={1} />
|
||||
{item.memberName}
|
||||
</Flex>
|
||||
),
|
||||
value: item.tmbId
|
||||
})),
|
||||
[members]
|
||||
);
|
||||
|
||||
const {
|
||||
data: bills,
|
||||
isLoading,
|
||||
Pagination,
|
||||
getData,
|
||||
total
|
||||
} = usePagination<BillSchemaType>({
|
||||
api: getBills,
|
||||
pageSize: 20,
|
||||
getData
|
||||
} = usePagination<BillItemType>({
|
||||
api: getUserBills,
|
||||
pageSize: isPc ? 20 : 10,
|
||||
params: {
|
||||
type: billType
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
source: billSource,
|
||||
teamMemberId: selectTmbId
|
||||
},
|
||||
defaultRequest: false
|
||||
});
|
||||
|
||||
const { mutate: handleRefreshPayOrder, isLoading: isRefreshing } = useRequest({
|
||||
mutationFn: async (payId: string) => {
|
||||
try {
|
||||
const data = await checkBalancePayResult(payId);
|
||||
toast({
|
||||
title: data,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: error?.message,
|
||||
status: 'warning'
|
||||
});
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
getData(1);
|
||||
} catch (error) {}
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getData(1);
|
||||
}, [billType]);
|
||||
}, [billSource, selectTmbId]);
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
isLoading={isLoading || isRefreshing}
|
||||
position={'relative'}
|
||||
h={'100%'}
|
||||
overflow={'overlay'}
|
||||
py={[0, 5]}
|
||||
px={[3, 8]}
|
||||
>
|
||||
<TableContainer>
|
||||
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
|
||||
<Flex
|
||||
flexDir={['column', 'row']}
|
||||
gap={2}
|
||||
w={'100%'}
|
||||
px={[3, 8]}
|
||||
alignItems={['flex-end', 'center']}
|
||||
>
|
||||
{tmbList.length > 1 && userInfo?.team?.canWrite && (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box mr={2} flexShrink={0}>
|
||||
{t('support.user.team.member')}
|
||||
</Box>
|
||||
<MySelect
|
||||
size={'sm'}
|
||||
minW={'100px'}
|
||||
list={tmbList}
|
||||
value={selectTmbId}
|
||||
onchange={setSelectTmbId}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Box flex={'1'} />
|
||||
<Flex alignItems={'center'} gap={3}>
|
||||
<DateRangePicker
|
||||
defaultDate={dateRange}
|
||||
position="bottom"
|
||||
onChange={setDateRange}
|
||||
onSuccess={() => getData(1)}
|
||||
/>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>#</Th>
|
||||
{/* <Th>{t('user.team.Member Name')}</Th> */}
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>
|
||||
<MySelect
|
||||
list={billTypeList}
|
||||
value={billType}
|
||||
list={sourceList}
|
||||
value={billSource}
|
||||
size={'sm'}
|
||||
onchange={(e) => {
|
||||
setBillType(e);
|
||||
setBillSource(e);
|
||||
}}
|
||||
w={'130px'}
|
||||
></MySelect>
|
||||
</Th>
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>{t('support.wallet.Amount')}</Th>
|
||||
<Th>{t('support.wallet.bill.Status')}</Th>
|
||||
<Th>{t('user.Application Name')}</Th>
|
||||
<Th>{t('user.Total Amount')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{bills.map((item, i) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{i + 1}</Td>
|
||||
<Td>{t(billTypeMap[item.type]?.label)}</Td>
|
||||
{bills.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
{/* <Td>{item.memberName}</Td> */}
|
||||
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
|
||||
<Td>{t(BillSourceMap[item.source]?.label)}</Td>
|
||||
<Td>{t(item.appName) || '-'}</Td>
|
||||
<Td>{item.total}元</Td>
|
||||
<Td>
|
||||
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
|
||||
</Td>
|
||||
<Td>{formatStorePrice2Read(item.price)}元</Td>
|
||||
<Td>{t(billStatusMap[item.status]?.label)}</Td>
|
||||
<Td>
|
||||
{item.status === 'NOTPAY' && (
|
||||
<Button mr={4} onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
|
||||
{t('common.Update')}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant={'whiteBase'} size={'sm'} onClick={() => setBillDetail(item)}>
|
||||
{t('common.Detail')}
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={() => setBillDetail(item)}>
|
||||
详情
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
{total >= 20 && (
|
||||
<Flex mt={3} justifyContent={'flex-end'}>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
)}
|
||||
{!isLoading && bills.length === 0 && (
|
||||
<Flex
|
||||
mt={'20vh'}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
{t('support.wallet.noBill')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</TableContainer>
|
||||
|
||||
{!!billDetail && (
|
||||
<BillDetailModal bill={billDetail} onClose={() => setBillDetail(undefined)} />
|
||||
{!isLoading && bills.length === 0 && (
|
||||
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
无使用记录~
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
{!!billDetail && <BillDetail bill={billDetail} onClose={() => setBillDetail(undefined)} />}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default BillTable;
|
||||
|
||||
function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('support.wallet.usage.Usage Detail')}
|
||||
maxW={['90vw', '700px']}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Number')}:</Box>
|
||||
<Box>{bill.orderId}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.usage.Time')}:</Box>
|
||||
<Box>{dayjs(bill.createTime).format('YYYY/MM/DD HH:mm:ss')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Status')}:</Box>
|
||||
<Box>{t(billStatusMap[bill.status]?.label)}</Box>
|
||||
</Flex>
|
||||
{!!bill.metadata?.payWay && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.bill.payWay.Way')}:</Box>
|
||||
<Box>{t(billPayWayMap[bill.metadata.payWay]?.label)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.Amount')}:</Box>
|
||||
<Box>{formatStorePrice2Read(bill.price)}元</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.bill.Type')}:</Box>
|
||||
<Box>{t(billTypeMap[bill.type]?.label)}</Box>
|
||||
</Flex>
|
||||
{!!bill.metadata?.subMode && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.mode.Period')}:</Box>
|
||||
<Box>{t(subModeMap[bill.metadata.subMode]?.label)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!bill.metadata?.standSubLevel && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Stand plan level')}:</Box>
|
||||
<Box>{t(standardSubLevelMap[bill.metadata.standSubLevel]?.label)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{bill.metadata?.datasetSize !== undefined && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Extra dataset size')}:</Box>
|
||||
<Box>{bill.metadata?.datasetSize}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{bill.metadata?.extraPoints !== undefined && (
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 120px'}>{t('support.wallet.subscription.Extra ai points')}:</Box>
|
||||
<Box>{bill.metadata.extraPoints}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
export default React.memo(BillTable);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
useDisclosure,
|
||||
useTheme,
|
||||
Divider,
|
||||
Select,
|
||||
Input,
|
||||
Link,
|
||||
Progress,
|
||||
Grid
|
||||
Progress
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
@@ -21,68 +22,35 @@ import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { timezoneList } from '@fastgpt/global/common/time/timezone';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { langMap, setLngStore } from '@/web/common/utils/i18n';
|
||||
import { useRouter } from 'next/router';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import MySelect from '@/components/Select';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { putUpdateMemberName } from '@/web/support/user/team/api';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { getTeamDatasetValidSub } from '@/web/support/wallet/sub/api';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import { AI_POINT_USAGE_CARD_ROUTE } from '@/web/support/wallet/sub/constants';
|
||||
|
||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
|
||||
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
|
||||
const PayModal = dynamic(() => import('./PayModal'));
|
||||
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
|
||||
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
|
||||
const SubDatasetModal = dynamic(() => import('@/components/support/wallet/SubDatasetModal'));
|
||||
|
||||
const Account = () => {
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
const { initUserInfo } = useUserStore();
|
||||
|
||||
useQuery(['init'], initUserInfo);
|
||||
|
||||
return (
|
||||
<Box py={[3, '28px']} px={['5vw', '64px']}>
|
||||
{isPc ? (
|
||||
<Flex justifyContent={'center'}>
|
||||
<Box flex={'0 0 330px'}>
|
||||
<MyInfo />
|
||||
<Box mt={9}>
|
||||
<Other />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box ml={'45px'} flex={'1 0 0'} maxW={'600px'}>
|
||||
<PlanUsage />
|
||||
</Box>
|
||||
</Flex>
|
||||
) : (
|
||||
<>
|
||||
<MyInfo />
|
||||
<PlanUsage />
|
||||
<Other />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Account);
|
||||
|
||||
const MyInfo = () => {
|
||||
const UserInfo = () => {
|
||||
const theme = useTheme();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, updateUserInfo } = useUserStore();
|
||||
const router = useRouter();
|
||||
const { feConfigs, systemVersion } = useSystemStore();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
|
||||
const timezones = useRef(timezoneList());
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
const { isPc } = useSystemStore();
|
||||
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
@@ -95,11 +63,13 @@ const MyInfo = () => {
|
||||
onClose: onCloseUpdatePsw,
|
||||
onOpen: onOpenUpdatePsw
|
||||
} = useDisclosure();
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenStandardModal,
|
||||
onClose: onCloseStandardModal,
|
||||
onOpen: onOpenStandardModal
|
||||
isOpen: isOpenSubDatasetModal,
|
||||
onClose: onCloseSubDatasetModal,
|
||||
onOpen: onOpenSubDatasetModal
|
||||
} = useDisclosure();
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
multiple: false
|
||||
@@ -147,73 +117,81 @@ const MyInfo = () => {
|
||||
[onclickSave, t, toast, userInfo]
|
||||
);
|
||||
|
||||
useQuery(['init'], initUserInfo, {
|
||||
onSuccess(res) {
|
||||
reset(res);
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
data: teamSubPlan = { totalPoints: 0, usedPoints: 0, datasetMaxSize: 800, usedDatasetSize: 0 }
|
||||
} = useQuery(['getTeamDatasetValidSub'], getTeamDatasetValidSub);
|
||||
const datasetUsageMap = useMemo(() => {
|
||||
const rate = teamSubPlan.usedDatasetSize / teamSubPlan.datasetMaxSize;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamSubPlan.datasetMaxSize || t('common.Unlimited'),
|
||||
usedSize: teamSubPlan.usedDatasetSize
|
||||
};
|
||||
}, [teamSubPlan.usedDatasetSize, teamSubPlan.datasetMaxSize, t]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{/* user info */}
|
||||
{isPc && (
|
||||
<Flex alignItems={'center'} fontSize={'xl'} h={'30px'}>
|
||||
<MyIcon mr={2} name={'acount/user'} w={'20px'} />
|
||||
{t('support.user.User self info')}
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Box mt={[0, 6]}>
|
||||
{isPc ? (
|
||||
<Flex alignItems={'center'} cursor={'pointer'}>
|
||||
<Box flex={'0 0 80px'}>{t('support.user.Avatar')}: </Box>
|
||||
|
||||
<MyTooltip label={t('common.avatar.Select Avatar')}>
|
||||
<Box
|
||||
w={['44px', '56px']}
|
||||
h={['44px', '56px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
onClick={onOpenSelectFile}
|
||||
>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenSelectFile}
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
py={[2, 10]}
|
||||
justifyContent={'center'}
|
||||
alignItems={'flex-start'}
|
||||
>
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenSelectFile}
|
||||
>
|
||||
<MyTooltip label={'更换头像'}>
|
||||
<Box
|
||||
w={['44px', '54px']}
|
||||
h={['44px', '54px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
>
|
||||
<MyTooltip label={'更换头像'}>
|
||||
<Box
|
||||
w={['44px', '54px']}
|
||||
h={['44px', '54px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
|
||||
<MyIcon mr={1} name={'edit'} w={'14px'} />
|
||||
{t('user.Replace')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
|
||||
<MyIcon mr={1} name={'edit'} w={'14px'} />
|
||||
{t('user.Replace')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
display={['flex', 'block']}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
ml={[0, 10]}
|
||||
mt={[6, 0]}
|
||||
>
|
||||
{feConfigs.isPlus && (
|
||||
<Flex mt={[0, 4]} alignItems={'center'}>
|
||||
<Flex mb={4} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Member Name')}: </Box>
|
||||
<Input
|
||||
flex={'1 0 0'}
|
||||
flex={1}
|
||||
defaultValue={userInfo?.team?.memberName || 'Member'}
|
||||
title={t('user.Edit name')}
|
||||
borderColor={'transparent'}
|
||||
pl={'10px'}
|
||||
transform={'translateX(-11px)'}
|
||||
maxLength={20}
|
||||
onBlur={(e) => {
|
||||
@@ -226,265 +204,109 @@ const MyInfo = () => {
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} mt={6}>
|
||||
<Flex alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
{feConfigs.isPlus && (
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Team')}: </Box>
|
||||
<Box flex={1}>
|
||||
<TeamMenu />
|
||||
</Box>
|
||||
</Flex>
|
||||
{feConfigs.isPlus && (
|
||||
<Box mt={6} whiteSpace={'nowrap'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'} fontSize={'md'}>
|
||||
{t('user.team.Balance')}:
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
|
||||
<Button size={'sm'} ml={5} onClick={onOpenPayModal}>
|
||||
{t('user.Pay')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const PlanUsage = () => {
|
||||
const { isPc } = useSystemStore();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, initUserInfo, teamPlanStatus } = useUserStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
|
||||
const planName = useMemo(() => {
|
||||
if (!teamPlanStatus?.standard?.currentSubLevel) return '';
|
||||
return standardSubLevelMap[teamPlanStatus.standard.currentSubLevel].label;
|
||||
}, [teamPlanStatus?.standard?.currentSubLevel]);
|
||||
const standardPlan = teamPlanStatus?.standard;
|
||||
|
||||
useQuery(['init'], initUserInfo, {
|
||||
onSuccess(res) {
|
||||
reset(res);
|
||||
}
|
||||
});
|
||||
|
||||
const datasetUsageMap = useMemo(() => {
|
||||
if (!teamPlanStatus) {
|
||||
return {
|
||||
colorScheme: 'green',
|
||||
value: 0,
|
||||
maxSize: t('common.Unlimited'),
|
||||
usedSize: 0
|
||||
};
|
||||
}
|
||||
const rate = teamPlanStatus.usedDatasetSize / teamPlanStatus.datasetMaxSize;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamPlanStatus.datasetMaxSize || t('common.Unlimited'),
|
||||
usedSize: teamPlanStatus.usedDatasetSize
|
||||
};
|
||||
}, [teamPlanStatus, t]);
|
||||
const aiPointsUsageMap = useMemo(() => {
|
||||
if (!teamPlanStatus) {
|
||||
return {
|
||||
colorScheme: 'green',
|
||||
value: 0,
|
||||
maxSize: t('common.Unlimited'),
|
||||
usedSize: 0
|
||||
};
|
||||
}
|
||||
|
||||
const rate = teamPlanStatus.usedPoints / teamPlanStatus.totalPoints;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamPlanStatus.totalPoints || t('common.Unlimited'),
|
||||
usedSize: teamPlanStatus.usedPoints
|
||||
};
|
||||
}, [teamPlanStatus, t]);
|
||||
|
||||
return standardPlan ? (
|
||||
<Box mt={[6, 0]}>
|
||||
<Flex fontSize={'xl'} h={'30px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon mr={2} name={'acount/plans'} w={'20px'} />
|
||||
{t('support.wallet.subscription.Team plan and usage')}
|
||||
</Flex>
|
||||
<Button
|
||||
ml={4}
|
||||
variant={'whitePrimary'}
|
||||
size={'sm'}
|
||||
onClick={() => router.push(AI_POINT_USAGE_CARD_ROUTE)}
|
||||
>
|
||||
{t('support.user.Price')}
|
||||
</Button>
|
||||
{/* <Button ml={4} variant={'whitePrimary'} size={'sm'}>
|
||||
套餐详情
|
||||
</Button> */}
|
||||
</Flex>
|
||||
<Box
|
||||
mt={[3, 6]}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Flex px={[5, 10]} py={[3, 6]}>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Language')}: </Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<Box color={'myGray.600'} fontSize="sm">
|
||||
{t('support.wallet.subscription.Current plan')}
|
||||
</Box>
|
||||
<Box fontWeight={'bold'} fontSize="xl">
|
||||
{t(planName)}
|
||||
</Box>
|
||||
<Flex mt="3" color={'#485264'} fontSize="sm">
|
||||
<Box>{t('common.Expired Time')}:</Box>
|
||||
<Box ml={2}>{formatTime2YMDHM(standardPlan?.expiredTime)}</Box>
|
||||
</Flex>
|
||||
<MySelect
|
||||
value={i18n.language}
|
||||
list={Object.entries(langMap).map(([key, lang]) => ({
|
||||
label: lang.label,
|
||||
value: key
|
||||
}))}
|
||||
onchange={(val: any) => {
|
||||
const lang = val;
|
||||
setLngStore(lang);
|
||||
router.replace(router.basePath, router.asPath, { locale: lang });
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Button onClick={() => router.push('/price')}>
|
||||
{t('support.wallet.subscription.Upgrade plan')}
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Timezone')}: </Box>
|
||||
<Select
|
||||
value={userInfo?.timezone}
|
||||
onChange={(e) => {
|
||||
if (!userInfo) return;
|
||||
onclickSave({ ...userInfo, timezone: e.target.value });
|
||||
}}
|
||||
>
|
||||
{timezones.current.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={['sm', 'md']} variant={'whitePrimary'} ml={5} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Box py={3} borderTopWidth={'1px'} borderTopColor={'borderColor.base'}>
|
||||
<Box py={[0, 3]} px={[5, 10]} overflow={'auto'}>
|
||||
<StandardPlanContentList
|
||||
level={standardPlan?.currentSubLevel}
|
||||
mode={standardPlan.currentMode}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
mt={6}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
borderRadius={'md'}
|
||||
px={[5, 10]}
|
||||
py={[4, 7]}
|
||||
>
|
||||
<Box width={'100%'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontWeight={'bold'}>{t('support.user.team.Dataset usage')}</Box>
|
||||
<Box color={'myGray.600'} ml={2}>
|
||||
{datasetUsageMap.usedSize}/{datasetUsageMap.maxSize}
|
||||
{feConfigs.isPlus && (
|
||||
<>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'} fontSize={'md'}>
|
||||
{t('user.team.Balance')}:
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
|
||||
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
|
||||
{t('user.Pay')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
{feConfigs?.show_pay && (
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontSize={'md'}>
|
||||
{t('support.user.team.Dataset usage')}: {datasetUsageMap.usedSize}/
|
||||
{datasetUsageMap.maxSize}
|
||||
</Box>
|
||||
{userInfo?.team?.canWrite && (
|
||||
<Button size={'sm'} onClick={onOpenSubDatasetModal}>
|
||||
{t('support.wallet.Buy more')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt={1}>
|
||||
<Progress
|
||||
value={datasetUsageMap.value}
|
||||
colorScheme={datasetUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.base'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Progress
|
||||
value={datasetUsageMap.value}
|
||||
colorScheme={datasetUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mt="9" width={'100%'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontWeight={'bold'}>{t('support.wallet.subscription.AI points')}</Box>
|
||||
<Box color={'myGray.600'} ml={2}>
|
||||
{Math.round(teamPlanStatus.usedPoints)}/{teamPlanStatus.totalPoints}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box mt={3}>
|
||||
<Progress
|
||||
value={aiPointsUsageMap.value}
|
||||
colorScheme={aiPointsUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex></Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
const Other = () => {
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { feConfigs, systemVersion } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, updateUserInfo, initUserInfo, teamPlanStatus } = useUserStore();
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
|
||||
const onclickSave = useCallback(
|
||||
async (data: UserType) => {
|
||||
await updateUserInfo({
|
||||
avatar: data.avatar,
|
||||
timezone: data.timezone,
|
||||
openaiAccount: data.openaiAccount
|
||||
});
|
||||
reset(data);
|
||||
toast({
|
||||
title: '更新数据成功',
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
[reset, toast, updateUserInfo]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Grid gridGap={4} mt={3}>
|
||||
{feConfigs?.docUrl && (
|
||||
<Link
|
||||
bg={'white'}
|
||||
href={getDocPath('/docs/intro')}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
@@ -504,53 +326,64 @@ const Other = () => {
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href={feConfigs.chatbotUrl}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
py={3}
|
||||
px={6}
|
||||
bg={'white'}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
textDecoration={'none !important'}
|
||||
>
|
||||
<MyIcon name={'core/app/aiLight'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('common.system.Help Chatbot')}
|
||||
</Box>
|
||||
</Link>
|
||||
|
||||
{feConfigs?.show_openai_account && (
|
||||
<Flex
|
||||
bg={'white'}
|
||||
py={4}
|
||||
{feConfigs?.chatbotUrl && (
|
||||
<Link
|
||||
href={feConfigs.chatbotUrl}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
onClick={onOpenOpenai}
|
||||
textDecoration={'none !important'}
|
||||
>
|
||||
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
|
||||
<MyIcon name={'core/app/aiLight'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
OpenAI/OneAPI 账号
|
||||
{t('common.system.Help Chatbot')}
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
</Link>
|
||||
)}
|
||||
</Grid>
|
||||
{feConfigs?.show_openai_account && (
|
||||
<>
|
||||
<Divider my={3} />
|
||||
|
||||
<MyTooltip label={'点击配置账号'}>
|
||||
<Flex
|
||||
w={['85%', '300px']}
|
||||
py={4}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
bg={'myWhite.300'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
onClick={onOpenOpenai}
|
||||
>
|
||||
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
OpenAI/OneAPI 账号
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
{isOpenOpenai && userInfo && (
|
||||
<OpenAIAccountModal
|
||||
defaultData={userInfo?.openaiAccount}
|
||||
@@ -563,6 +396,10 @@ const Other = () => {
|
||||
onClose={onCloseOpenai}
|
||||
/>
|
||||
)}
|
||||
{isOpenSubDatasetModal && <SubDatasetModal onClose={onCloseSubDatasetModal} />}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UserInfo);
|
||||
|
||||
@@ -1,439 +0,0 @@
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
useDisclosure,
|
||||
useTheme,
|
||||
Divider,
|
||||
Select,
|
||||
Input,
|
||||
Link,
|
||||
Progress
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { timezoneList } from '@fastgpt/global/common/time/timezone';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { langMap, setLngStore } from '@/web/common/utils/i18n';
|
||||
import { useRouter } from 'next/router';
|
||||
import MySelect from '@/components/Select';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { putUpdateMemberName } from '@/web/support/user/team/api';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import { getTeamPlanStatus } from '@/web/support/wallet/sub/api';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
|
||||
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
|
||||
const PayModal = dynamic(() => import('./PayModal'));
|
||||
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
|
||||
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
|
||||
|
||||
const UserInfo = () => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
const { feConfigs, systemVersion } = useSystemStore();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
|
||||
const timezones = useRef(timezoneList());
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
|
||||
const { toast } = useToast();
|
||||
const {
|
||||
isOpen: isOpenPayModal,
|
||||
onClose: onClosePayModal,
|
||||
onOpen: onOpenPayModal
|
||||
} = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenUpdatePsw,
|
||||
onClose: onCloseUpdatePsw,
|
||||
onOpen: onOpenUpdatePsw
|
||||
} = useDisclosure();
|
||||
const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
multiple: false
|
||||
});
|
||||
|
||||
const onclickSave = useCallback(
|
||||
async (data: UserType) => {
|
||||
await updateUserInfo({
|
||||
avatar: data.avatar,
|
||||
timezone: data.timezone,
|
||||
openaiAccount: data.openaiAccount
|
||||
});
|
||||
reset(data);
|
||||
toast({
|
||||
title: '更新数据成功',
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
[reset, toast, updateUserInfo]
|
||||
);
|
||||
|
||||
const onSelectFile = useCallback(
|
||||
async (e: File[]) => {
|
||||
const file = e[0];
|
||||
if (!file || !userInfo) return;
|
||||
try {
|
||||
const src = await compressImgFileAndUpload({
|
||||
type: MongoImageTypeEnum.userAvatar,
|
||||
file,
|
||||
maxW: 300,
|
||||
maxH: 300
|
||||
});
|
||||
|
||||
onclickSave({
|
||||
...userInfo,
|
||||
avatar: src
|
||||
});
|
||||
} catch (err: any) {
|
||||
toast({
|
||||
title: typeof err === 'string' ? err : t('common.error.Select avatar failed'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
},
|
||||
[onclickSave, t, toast, userInfo]
|
||||
);
|
||||
|
||||
useQuery(['init'], initUserInfo, {
|
||||
onSuccess(res) {
|
||||
reset(res);
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
data: teamSubPlan = {
|
||||
totalPoints: 0,
|
||||
usedPoints: 0,
|
||||
datasetMaxSize: 800,
|
||||
usedDatasetSize: 0
|
||||
}
|
||||
} = useQuery(['getTeamPlanStatus'], getTeamPlanStatus);
|
||||
const datasetUsageMap = useMemo(() => {
|
||||
const rate = teamSubPlan.usedDatasetSize / teamSubPlan.datasetMaxSize;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamSubPlan.datasetMaxSize || t('common.Unlimited'),
|
||||
usedSize: teamSubPlan.usedDatasetSize
|
||||
};
|
||||
}, [teamSubPlan.usedDatasetSize, teamSubPlan.datasetMaxSize, t]);
|
||||
const aiPointsUsageMap = useMemo(() => {
|
||||
const rate = teamSubPlan.usedPoints / teamSubPlan.totalPoints;
|
||||
|
||||
const colorScheme = (() => {
|
||||
if (rate < 0.5) return 'green';
|
||||
if (rate < 0.8) return 'yellow';
|
||||
return 'red';
|
||||
})();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
value: rate * 100,
|
||||
maxSize: teamSubPlan.totalPoints || t('common.Unlimited'),
|
||||
usedSize: teamSubPlan.usedPoints
|
||||
};
|
||||
}, [teamSubPlan.usedPoints, teamSubPlan.totalPoints, t]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
py={[2, 10]}
|
||||
justifyContent={'center'}
|
||||
alignItems={'flex-start'}
|
||||
>
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenSelectFile}
|
||||
>
|
||||
<MyTooltip label={'更换头像'}>
|
||||
<Box
|
||||
w={['44px', '54px']}
|
||||
h={['44px', '54px']}
|
||||
borderRadius={'50%'}
|
||||
border={theme.borders.base}
|
||||
overflow={'hidden'}
|
||||
p={'2px'}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
mb={2}
|
||||
>
|
||||
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'}>
|
||||
<MyIcon mr={1} name={'edit'} w={'14px'} />
|
||||
{t('user.Replace')}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
display={['flex', 'block']}
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
ml={[0, 10]}
|
||||
mt={[6, 0]}
|
||||
>
|
||||
{feConfigs.isPlus && (
|
||||
<Flex mb={4} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Member Name')}: </Box>
|
||||
<Input
|
||||
flex={1}
|
||||
defaultValue={userInfo?.team?.memberName || 'Member'}
|
||||
title={t('user.Edit name')}
|
||||
borderColor={'transparent'}
|
||||
pl={'10px'}
|
||||
transform={'translateX(-11px)'}
|
||||
maxLength={20}
|
||||
onBlur={(e) => {
|
||||
const val = e.target.value;
|
||||
if (val === userInfo?.team?.memberName) return;
|
||||
try {
|
||||
putUpdateMemberName(val);
|
||||
} catch (error) {}
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Team')}: </Box>
|
||||
<Box flex={1}>
|
||||
<TeamMenu />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Language')}: </Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
value={i18n.language}
|
||||
list={Object.entries(langMap).map(([key, lang]) => ({
|
||||
label: lang.label,
|
||||
value: key
|
||||
}))}
|
||||
onchange={(val: any) => {
|
||||
const lang = val;
|
||||
setLngStore(lang);
|
||||
router.replace(router.basePath, router.asPath, { locale: lang });
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Timezone')}: </Box>
|
||||
<Select
|
||||
value={userInfo?.timezone}
|
||||
onChange={(e) => {
|
||||
if (!userInfo) return;
|
||||
onclickSave({ ...userInfo, timezone: e.target.value });
|
||||
}}
|
||||
>
|
||||
{timezones.current.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={['sm', 'md']} variant={'whitePrimary'} ml={5} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
</Button>
|
||||
</Flex>
|
||||
{feConfigs.isPlus && (
|
||||
<>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'} fontSize={'md'}>
|
||||
{t('user.team.Balance')}:
|
||||
</Box>
|
||||
<Box flex={1}>
|
||||
<strong>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
{feConfigs?.show_pay && userInfo?.team?.canWrite && (
|
||||
<Button size={['sm', 'md']} ml={5} onClick={onOpenPayModal}>
|
||||
{t('user.Pay')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
{feConfigs?.show_pay && (
|
||||
<>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontSize={'md'}>
|
||||
{t('support.user.team.Dataset usage')}: {datasetUsageMap.usedSize}/
|
||||
{datasetUsageMap.maxSize}
|
||||
</Box>
|
||||
{userInfo?.team?.canWrite && (
|
||||
<Button size={'sm'} onClick={() => router.push('/price')}>
|
||||
{t('support.wallet.Buy more')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Box mt={1}>
|
||||
<Progress
|
||||
value={datasetUsageMap.value}
|
||||
colorScheme={datasetUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.base'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontSize={'md'}>
|
||||
AI积分: {Math.round(teamSubPlan.usedPoints)}/{teamSubPlan.totalPoints}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={1}>
|
||||
<Progress
|
||||
value={aiPointsUsageMap.value}
|
||||
colorScheme={aiPointsUsageMap.colorScheme}
|
||||
borderRadius={'md'}
|
||||
isAnimated
|
||||
hasStripe
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.base'}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{feConfigs?.docUrl && (
|
||||
<Link
|
||||
href={getDocPath('/docs/intro')}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
textDecoration={'none !important'}
|
||||
>
|
||||
<MyIcon name={'common/courseLight'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('system.Help Document')}
|
||||
</Box>
|
||||
<Box w={'8px'} h={'8px'} borderRadius={'50%'} bg={'#67c13b'} />
|
||||
<Box fontSize={'md'} ml={2}>
|
||||
V{systemVersion}
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
{feConfigs?.chatbotUrl && (
|
||||
<Link
|
||||
href={feConfigs.chatbotUrl}
|
||||
target="_blank"
|
||||
display={'flex'}
|
||||
mt={4}
|
||||
w={['85%', '300px']}
|
||||
py={3}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
userSelect={'none'}
|
||||
textDecoration={'none !important'}
|
||||
>
|
||||
<MyIcon name={'core/app/aiLight'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
{t('common.system.Help Chatbot')}
|
||||
</Box>
|
||||
</Link>
|
||||
)}
|
||||
{feConfigs?.show_openai_account && (
|
||||
<>
|
||||
<Divider my={3} />
|
||||
|
||||
<MyTooltip label={'点击配置账号'}>
|
||||
<Flex
|
||||
w={['85%', '300px']}
|
||||
py={4}
|
||||
px={6}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
bg={'myWhite.300'}
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
onClick={onOpenOpenai}
|
||||
>
|
||||
<MyIcon name={'common/openai'} w={'18px'} color={'myGray.600'} />
|
||||
<Box ml={2} flex={1}>
|
||||
OpenAI/OneAPI 账号
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
h={'9px'}
|
||||
borderRadius={'50%'}
|
||||
bg={userInfo?.openaiAccount?.key ? '#67c13b' : 'myGray.500'}
|
||||
/>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
|
||||
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
|
||||
{isOpenOpenai && userInfo && (
|
||||
<OpenAIAccountModal
|
||||
defaultData={userInfo?.openaiAccount}
|
||||
onSuccess={(data) =>
|
||||
onclickSave({
|
||||
...userInfo,
|
||||
openaiAccount: data
|
||||
})
|
||||
}
|
||||
onClose={onCloseOpenai}
|
||||
/>
|
||||
)}
|
||||
<File onSelect={onSelectFile} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UserInfo);
|
||||
@@ -1,45 +1,37 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { ModalFooter, ModalBody, Button, Input, Box, Grid } from '@chakra-ui/react';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
|
||||
import { getPayCode, checkPayResult } from '@/web/support/wallet/pay/api';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
|
||||
const PayModal = ({
|
||||
onClose,
|
||||
defaultValue,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultValue?: number;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => any;
|
||||
}) => {
|
||||
const PayModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [inputVal, setInputVal] = useState<number | undefined>(defaultValue);
|
||||
const [inputVal, setInputVal] = useState<number | ''>('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
const [payId, setPayId] = useState('');
|
||||
|
||||
const handleClickPay = useCallback(async () => {
|
||||
if (!inputVal || inputVal <= 0 || isNaN(+inputVal)) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
// 获取支付二维码
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.balance,
|
||||
balance: inputVal
|
||||
});
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
const res = await getPayCode(inputVal);
|
||||
new window.QRCode(document.getElementById('payQRCode'), {
|
||||
text: res.codeUrl,
|
||||
width: 128,
|
||||
height: 128,
|
||||
colorDark: '#000000',
|
||||
colorLight: '#ffffff',
|
||||
correctLevel: window.QRCode.CorrectLevel.H
|
||||
});
|
||||
setPayId(res.payId);
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
@@ -49,48 +41,84 @@ const PayModal = ({
|
||||
setLoading(false);
|
||||
}, [inputVal, toast]);
|
||||
|
||||
useQuery(
|
||||
[payId],
|
||||
() => {
|
||||
if (!payId) return null;
|
||||
return checkPayResult(payId);
|
||||
},
|
||||
{
|
||||
enabled: !!payId,
|
||||
refetchInterval: 3000,
|
||||
onSuccess(res) {
|
||||
if (!res) return;
|
||||
toast({
|
||||
title: res,
|
||||
status: 'success'
|
||||
});
|
||||
router.reload();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} title={t('user.Pay')} iconSrc="/imgs/modal/pay.svg">
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={payId ? undefined : onClose}
|
||||
title={t('user.Pay')}
|
||||
iconSrc="/imgs/modal/pay.svg"
|
||||
>
|
||||
<ModalBody px={0} display={'flex'} flexDirection={'column'}>
|
||||
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
|
||||
{[10, 20, 50, 100, 200, 500].map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
variant={item === inputVal ? 'solid' : 'outline'}
|
||||
onClick={() => setInputVal(item)}
|
||||
>
|
||||
{item}元
|
||||
</Button>
|
||||
))}
|
||||
</Grid>
|
||||
<Box px={6}>
|
||||
<Input
|
||||
value={inputVal}
|
||||
type={'number'}
|
||||
step={1}
|
||||
placeholder={'其他金额,请取整数'}
|
||||
onChange={(e) => {
|
||||
setInputVal(Math.floor(+e.target.value));
|
||||
}}
|
||||
></Input>
|
||||
{!payId && (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(3,1fr)'} gridGap={5} mb={4} px={6}>
|
||||
{[10, 20, 50, 100, 200, 500].map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
variant={item === inputVal ? 'solid' : 'outline'}
|
||||
onClick={() => setInputVal(item)}
|
||||
>
|
||||
{item}元
|
||||
</Button>
|
||||
))}
|
||||
</Grid>
|
||||
<Box px={6}>
|
||||
<Input
|
||||
value={inputVal}
|
||||
type={'number'}
|
||||
step={1}
|
||||
placeholder={'其他金额,请取整数'}
|
||||
onChange={(e) => {
|
||||
setInputVal(Math.floor(+e.target.value));
|
||||
}}
|
||||
></Input>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{/* 付费二维码 */}
|
||||
<Box textAlign={'center'}>
|
||||
{payId && <Box mb={3}>请微信扫码支付: {inputVal}元,请勿关闭页面</Box>}
|
||||
<Box id={'payQRCode'} display={'inline-block'}></Box>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
ml={3}
|
||||
isLoading={loading}
|
||||
isDisabled={!inputVal || inputVal === 0}
|
||||
onClick={handleClickPay}
|
||||
>
|
||||
获取充值二维码
|
||||
</Button>
|
||||
{!payId && (
|
||||
<>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button
|
||||
ml={3}
|
||||
isLoading={loading}
|
||||
isDisabled={!inputVal || inputVal === 0}
|
||||
onClick={handleClickPay}
|
||||
>
|
||||
获取充值二维码
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ModalFooter>
|
||||
|
||||
{!!qrPayData && <QRCodePayModal {...qrPayData} onSuccess={onSuccess} />}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
108
projects/app/src/pages/account/components/PayRecordTable.tsx
Normal file
108
projects/app/src/pages/account/components/PayRecordTable.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Flex,
|
||||
Box
|
||||
} from '@chakra-ui/react';
|
||||
import { getPayOrders, checkPayResult } from '@/web/support/wallet/pay/api';
|
||||
import type { PaySchema } from '@fastgpt/global/support/wallet/pay/type.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const PayRecordTable = () => {
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const [payOrders, setPayOrders] = useState<PaySchema[]>([]);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { isInitialLoading, refetch } = useQuery(['initPayOrder'], getPayOrders, {
|
||||
onSuccess(res) {
|
||||
setPayOrders(res);
|
||||
}
|
||||
});
|
||||
|
||||
const handleRefreshPayOrder = useCallback(
|
||||
async (payId: string) => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const data = await checkPayResult(payId);
|
||||
toast({
|
||||
title: data,
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: error?.message,
|
||||
status: 'warning'
|
||||
});
|
||||
console.log(error);
|
||||
}
|
||||
try {
|
||||
refetch();
|
||||
} catch (error) {}
|
||||
|
||||
setIsLoading(false);
|
||||
},
|
||||
[refetch, setIsLoading, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box position={'relative'} h={'100%'} overflow={'overlay'}>
|
||||
{!isInitialLoading && payOrders.length === 0 ? (
|
||||
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} justifyContent={'center'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
无支付记录~
|
||||
</Box>
|
||||
</Flex>
|
||||
) : (
|
||||
<TableContainer py={[0, 5]} px={[3, 8]}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>订单号</Th>
|
||||
<Th>时间</Th>
|
||||
<Th>金额</Th>
|
||||
<Th>状态</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{payOrders.map((item) => (
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.orderId}</Td>
|
||||
<Td>
|
||||
{item.createTime ? dayjs(item.createTime).format('YYYY/MM/DD HH:mm:ss') : '-'}
|
||||
</Td>
|
||||
<Td>{formatStorePrice2Read(item.price)}元</Td>
|
||||
<Td>{item.status}</Td>
|
||||
<Td>
|
||||
{item.status === 'NOTPAY' && (
|
||||
<Button onClick={() => handleRefreshPayOrder(item._id)} size={'sm'}>
|
||||
更新
|
||||
</Button>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
<Loading loading={isInitialLoading} fixed={false} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PayRecordTable;
|
||||
@@ -1,119 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import {
|
||||
ModalBody,
|
||||
Flex,
|
||||
Box,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer
|
||||
} from '@chakra-ui/react';
|
||||
import { UsageItemType } from '@fastgpt/global/support/wallet/usage/type.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
|
||||
const UsageDetail = ({ usage, onClose }: { usage: UsageItemType; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const filterBillList = useMemo(
|
||||
() => usage.list.filter((item) => item && item.moduleName),
|
||||
[usage.list]
|
||||
);
|
||||
|
||||
const { hasModel, hasCharsLen, hasDuration } = useMemo(() => {
|
||||
let hasModel = false;
|
||||
let hasCharsLen = false;
|
||||
let hasDuration = false;
|
||||
let hasDataLen = false;
|
||||
|
||||
usage.list.forEach((item) => {
|
||||
if (item.model !== undefined) {
|
||||
hasModel = true;
|
||||
}
|
||||
|
||||
if (typeof item.charsLength === 'number') {
|
||||
hasCharsLen = true;
|
||||
}
|
||||
if (typeof item.duration === 'number') {
|
||||
hasDuration = true;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
hasModel,
|
||||
hasCharsLen,
|
||||
hasDuration,
|
||||
hasDataLen
|
||||
};
|
||||
}, [usage.list]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/modal/bill.svg"
|
||||
title={t('support.wallet.usage.Usage Detail')}
|
||||
maxW={['90vw', '700px']}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.bill.Number')}:</Box>
|
||||
<Box>{usage.id}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Time')}:</Box>
|
||||
<Box>{dayjs(usage.time).format('YYYY/MM/DD HH:mm:ss')}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.usage.App name')}:</Box>
|
||||
<Box>{t(usage.appName) || '-'}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Source')}:</Box>
|
||||
<Box>{t(UsageSourceMap[usage.source]?.label)}</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} pb={4}>
|
||||
<Box flex={'0 0 80px'}>{t('support.wallet.usage.Total points')}:</Box>
|
||||
<Box fontWeight={'bold'}>{formatNumber(usage.totalPoints)}</Box>
|
||||
</Flex>
|
||||
<Box pb={4}>
|
||||
<Box flex={'0 0 80px'} mb={1}>
|
||||
{t('support.wallet.usage.Bill Module')}
|
||||
</Box>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('support.wallet.usage.Module name')}</Th>
|
||||
{hasModel && <Th>{t('support.wallet.usage.Ai model')}</Th>}
|
||||
{hasCharsLen && <Th>{t('support.wallet.usage.Text Length')}</Th>}
|
||||
{hasDuration && <Th>{t('support.wallet.usage.Duration')}</Th>}
|
||||
|
||||
<Th>{t('support.wallet.usage.Total points')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{filterBillList.map((item, i) => (
|
||||
<Tr key={i}>
|
||||
<Td>{t(item.moduleName)}</Td>
|
||||
{hasModel && <Td>{item.model ?? '-'}</Td>}
|
||||
{hasCharsLen && <Td>{item.charsLength ?? '-'}</Td>}
|
||||
{hasDuration && <Td>{item.duration ?? '-'}</Td>}
|
||||
<Td>{formatNumber(item.amount)}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsageDetail;
|
||||
@@ -1,189 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Flex,
|
||||
Box,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
import { UsageSourceEnum, UsageSourceMap } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { getUserUsages } from '@/web/support/wallet/usage/api';
|
||||
import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
|
||||
import { usePagination } from '@/web/common/hooks/usePagination';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import dayjs from 'dayjs';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
|
||||
import { addDays } from 'date-fns';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { formatNumber } from '../../../../../../packages/global/common/math/tools';
|
||||
const UsageDetail = dynamic(() => import('./UsageDetail'));
|
||||
|
||||
const UsageTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const { Loading } = useLoading();
|
||||
const [dateRange, setDateRange] = useState<DateRangeType>({
|
||||
from: addDays(new Date(), -7),
|
||||
to: new Date()
|
||||
});
|
||||
const [usageSource, setUsageSource] = useState<`${UsageSourceEnum}` | ''>('');
|
||||
const { isPc } = useSystemStore();
|
||||
const { userInfo } = useUserStore();
|
||||
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
|
||||
|
||||
const sourceList = useMemo(
|
||||
() => [
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(UsageSourceMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId);
|
||||
const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => {
|
||||
if (!userInfo?.team?.teamId) return [];
|
||||
return getTeamMembers(userInfo.team.teamId);
|
||||
});
|
||||
const tmbList = useMemo(
|
||||
() =>
|
||||
members.map((item) => ({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={item.avatar} w={'16px'} mr={1} />
|
||||
{item.memberName}
|
||||
</Flex>
|
||||
),
|
||||
value: item.tmbId
|
||||
})),
|
||||
[members]
|
||||
);
|
||||
|
||||
const {
|
||||
data: usages,
|
||||
isLoading,
|
||||
Pagination,
|
||||
getData
|
||||
} = usePagination<UsageItemType>({
|
||||
api: getUserUsages,
|
||||
pageSize: isPc ? 20 : 10,
|
||||
params: {
|
||||
dateStart: dateRange.from || new Date(),
|
||||
dateEnd: addDays(dateRange.to || new Date(), 1),
|
||||
source: usageSource,
|
||||
teamMemberId: selectTmbId
|
||||
},
|
||||
defaultRequest: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getData(1);
|
||||
}, [usageSource, selectTmbId]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
|
||||
<Flex
|
||||
flexDir={['column', 'row']}
|
||||
gap={2}
|
||||
w={'100%'}
|
||||
px={[3, 8]}
|
||||
alignItems={['flex-end', 'center']}
|
||||
>
|
||||
{tmbList.length > 1 && userInfo?.team?.canWrite && (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box mr={2} flexShrink={0}>
|
||||
{t('support.user.team.member')}
|
||||
</Box>
|
||||
<MySelect
|
||||
size={'sm'}
|
||||
minW={'100px'}
|
||||
list={tmbList}
|
||||
value={selectTmbId}
|
||||
onchange={setSelectTmbId}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
<Box flex={'1'} />
|
||||
<Flex alignItems={'center'} gap={3}>
|
||||
<DateRangePicker
|
||||
defaultDate={dateRange}
|
||||
position="bottom"
|
||||
onChange={setDateRange}
|
||||
onSuccess={() => getData(1)}
|
||||
/>
|
||||
<Pagination />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
{/* <Th>{t('user.team.Member Name')}</Th> */}
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>
|
||||
<MySelect
|
||||
list={sourceList}
|
||||
value={usageSource}
|
||||
size={'sm'}
|
||||
onchange={(e) => {
|
||||
setUsageSource(e);
|
||||
}}
|
||||
w={'130px'}
|
||||
></MySelect>
|
||||
</Th>
|
||||
<Th>{t('user.Application Name')}</Th>
|
||||
<Th>{t('support.wallet.usage.Total points')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody fontSize={'sm'}>
|
||||
{usages.map((item) => (
|
||||
<Tr key={item.id}>
|
||||
{/* <Td>{item.memberName}</Td> */}
|
||||
<Td>{dayjs(item.time).format('YYYY/MM/DD HH:mm:ss')}</Td>
|
||||
<Td>{t(UsageSourceMap[item.source]?.label) || '-'}</Td>
|
||||
<Td>{t(item.appName) || '-'}</Td>
|
||||
<Td>{formatNumber(item.totalPoints) || 0}</Td>
|
||||
<Td>
|
||||
<Button size={'sm'} variant={'whitePrimary'} onClick={() => setUsageDetail(item)}>
|
||||
详情
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{!isLoading && usages.length === 0 && (
|
||||
<Flex flex={'1 0 0'} flexDirection={'column'} alignItems={'center'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
无使用记录~
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Loading loading={isLoading} fixed={false} />
|
||||
{!!usageDetail && (
|
||||
<UsageDetail usage={usageDetail} onClose={() => setUsageDetail(undefined)} />
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UsageTable);
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import type { UserType } from '@fastgpt/global/support/user/type.d';
|
||||
|
||||
const StandDetailModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="acount/plansBlue"
|
||||
title={t('user.OpenAI Account Setting')}
|
||||
>
|
||||
<ModalBody></ModalBody>
|
||||
<ModalFooter></ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default StandDetailModal;
|
||||
@@ -14,16 +14,18 @@ import { useTranslation } from 'next-i18next';
|
||||
import Script from 'next/script';
|
||||
|
||||
const Promotion = dynamic(() => import('./components/Promotion'));
|
||||
const UsageTable = dynamic(() => import('./components/UsageTable'));
|
||||
const BillTable = dynamic(() => import('./components/BillTable'));
|
||||
const PayRecordTable = dynamic(() => import('./components/PayRecordTable'));
|
||||
const InformTable = dynamic(() => import('./components/InformTable'));
|
||||
const ApiKeyTable = dynamic(() => import('./components/ApiKeyTable'));
|
||||
const PriceBox = dynamic(() => import('@/components/support/wallet/Price'));
|
||||
|
||||
enum TabEnum {
|
||||
'info' = 'info',
|
||||
'promotion' = 'promotion',
|
||||
'usage' = 'usage',
|
||||
'bill' = 'bill',
|
||||
'price' = 'price',
|
||||
'pay' = 'pay',
|
||||
'inform' = 'inform',
|
||||
'apikey' = 'apikey',
|
||||
'loginout' = 'loginout'
|
||||
@@ -43,18 +45,27 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
...(feConfigs?.isPlus
|
||||
? [
|
||||
{
|
||||
icon: 'support/usage/usageRecordLight',
|
||||
icon: 'support/bill/billRecordLight',
|
||||
label: t('user.Usage Record'),
|
||||
id: TabEnum.usage
|
||||
id: TabEnum.bill
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.show_pay && userInfo?.team.canWrite
|
||||
? [
|
||||
{
|
||||
icon: 'support/bill/payRecordLight',
|
||||
label: t('support.wallet.Bills'),
|
||||
id: TabEnum.bill
|
||||
icon: 'support/pay/payRecordLight',
|
||||
label: t('user.Recharge Record'),
|
||||
id: TabEnum.pay
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.show_pay
|
||||
? [
|
||||
{
|
||||
icon: 'support/pay/priceLight',
|
||||
label: t('support.user.Price'),
|
||||
id: TabEnum.price
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -97,6 +108,11 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
content: '确认退出登录?'
|
||||
});
|
||||
const {
|
||||
isOpen: isOpenPriceBox,
|
||||
onOpen: onOpenPriceBox,
|
||||
onClose: onClosePriceBox
|
||||
} = useDisclosure();
|
||||
|
||||
const router = useRouter();
|
||||
const theme = useTheme();
|
||||
@@ -108,6 +124,8 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
setUserInfo(null);
|
||||
router.replace('/login');
|
||||
})();
|
||||
} else if (tab === TabEnum.price) {
|
||||
onOpenPriceBox();
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
@@ -116,7 +134,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
});
|
||||
}
|
||||
},
|
||||
[openConfirm, router, setUserInfo]
|
||||
[onOpenPriceBox, openConfirm, router, setUserInfo]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -160,14 +178,16 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
<Box flex={'1 0 0'} h={'100%'} pb={[4, 0]}>
|
||||
{currentTab === TabEnum.info && <UserInfo />}
|
||||
{currentTab === TabEnum.promotion && <Promotion />}
|
||||
{currentTab === TabEnum.usage && <UsageTable />}
|
||||
{currentTab === TabEnum.bill && <BillTable />}
|
||||
{currentTab === TabEnum.pay && <PayRecordTable />}
|
||||
{currentTab === TabEnum.inform && <InformTable />}
|
||||
{currentTab === TabEnum.apikey && <ApiKeyTable />}
|
||||
</Box>
|
||||
</Flex>
|
||||
<ConfirmModal />
|
||||
</PageContainer>
|
||||
|
||||
{isOpenPriceBox && <PriceBox onClose={onClosePriceBox} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
|
||||
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
|
||||
import { MongoImage } from '@fastgpt/service/common/file/image/schema';
|
||||
import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type';
|
||||
import { delay } from '@fastgpt/global/common/system/utils';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { DYNAMIC_INPUT_KEY, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
let success = 0;
|
||||
let deleteImg = 0;
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
// 设置所有app为 inited = false
|
||||
const result = await MongoApp.updateMany({}, { $set: { inited: false } });
|
||||
console.log(result);
|
||||
|
||||
await initApp();
|
||||
|
||||
jsonRes(res, {
|
||||
message: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const systemKeys: string[] = [
|
||||
ModuleInputKeyEnum.switch,
|
||||
ModuleInputKeyEnum.httpMethod,
|
||||
ModuleInputKeyEnum.httpReqUrl,
|
||||
ModuleInputKeyEnum.httpHeaders,
|
||||
DYNAMIC_INPUT_KEY,
|
||||
ModuleInputKeyEnum.addInputParam
|
||||
];
|
||||
const initApp = async (): Promise<any> => {
|
||||
const app = await MongoApp.findOne({ inited: false }).sort({ updateTime: -1 });
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const modules = JSON.parse(JSON.stringify(app.modules)) as ModuleItemType[];
|
||||
let update = false;
|
||||
// 找到http模块
|
||||
modules.forEach((module) => {
|
||||
if (module.flowType === 'httpRequest') {
|
||||
const method = module.inputs.find((input) => input.key === ModuleInputKeyEnum.httpMethod);
|
||||
if (method?.value === 'POST') {
|
||||
module.inputs.forEach((input) => {
|
||||
// 更新非系统字段的key
|
||||
if (!systemKeys.includes(input.key)) {
|
||||
// 更新output的target
|
||||
modules.forEach((item) => {
|
||||
item.outputs.forEach((output) => {
|
||||
output.targets.forEach((target) => {
|
||||
if (target.moduleId === module.moduleId && target.key === input.key) {
|
||||
target.key = `data.${input.key}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
// 更新key
|
||||
input.key = `data.${input.key}`;
|
||||
update = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (update) {
|
||||
console.log('update http app');
|
||||
app.modules = modules;
|
||||
}
|
||||
app.inited = true;
|
||||
await app.save();
|
||||
|
||||
console.log(++success);
|
||||
return initApp();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
await delay(1000);
|
||||
return initApp();
|
||||
}
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoUsage } from '@fastgpt/service/support/wallet/usage/schema';
|
||||
import { connectionMongo } from '@fastgpt/service/common/mongo';
|
||||
|
||||
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
await authCert({ req, authRoot: true });
|
||||
|
||||
// 检查 usage 是否有记录
|
||||
const totalUsages = await MongoUsage.countDocuments();
|
||||
if (totalUsages === 0) {
|
||||
// 重命名 bills 集合成 usages
|
||||
await connectionMongo.connection.db.renameCollection('bills', 'usages', {
|
||||
// 强制
|
||||
dropTarget: true
|
||||
});
|
||||
}
|
||||
|
||||
jsonRes(res, {
|
||||
message: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
let filePaths: string[] = [];
|
||||
|
||||
try {
|
||||
const { teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
const { userId, teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
|
||||
const { file, bucketName, metadata } = await upload.doUpload(req, res);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
|
||||
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
|
||||
import { pushQuestionGuideBill } from '@/service/support/wallet/bill/push';
|
||||
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
|
||||
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
|
||||
|
||||
@@ -19,7 +19,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
|
||||
const qgModel = global.llmModels[0];
|
||||
|
||||
const { result, charsLength } = await createQuestionGuide({
|
||||
const { result, inputTokens, outputTokens } = await createQuestionGuide({
|
||||
messages,
|
||||
model: qgModel.model
|
||||
});
|
||||
@@ -28,8 +28,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
data: result
|
||||
});
|
||||
|
||||
pushQuestionGuideUsage({
|
||||
charsLength,
|
||||
pushQuestionGuideBill({
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
teamId,
|
||||
tmbId
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { SimpleModeTemplate_FastGPT_Universal } from '@/global/core/app/constants';
|
||||
import { checkTeamAppLimit } from '@/service/support/permission/teamLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -26,7 +25,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
|
||||
|
||||
// 上限校验
|
||||
await checkTeamAppLimit(teamId);
|
||||
const authCount = await MongoApp.countDocuments({
|
||||
teamId
|
||||
});
|
||||
if (authCount >= 50) {
|
||||
throw new Error('每个团队上限 50 个应用');
|
||||
}
|
||||
|
||||
// 创建模型
|
||||
const response = await MongoApp.create({
|
||||
|
||||
51
projects/app/src/pages/api/core/app/data/totalUsage.ts
Normal file
51
projects/app/src/pages/api/core/app/data/totalUsage.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { Types } from '@fastgpt/service/common/mongo';
|
||||
import { MongoBill } from '@fastgpt/service/support/wallet/bill/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId, start, end } = req.body as { appId: string; start: number; end: number };
|
||||
const { userId } = await authCert({ req, authToken: true });
|
||||
|
||||
const result = await MongoBill.aggregate([
|
||||
{
|
||||
$match: {
|
||||
appId: new Types.ObjectId(appId),
|
||||
userId: new Types.ObjectId(userId),
|
||||
time: { $gte: new Date(start) }
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: { $year: '$time' },
|
||||
month: { $month: '$time' },
|
||||
day: { $dayOfMonth: '$time' }
|
||||
},
|
||||
total: { $sum: '$total' }
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
date: { $dateFromParts: { year: '$_id.year', month: '$_id.month', day: '$_id.day' } },
|
||||
total: 1
|
||||
}
|
||||
},
|
||||
{ $sort: { date: 1 } }
|
||||
]);
|
||||
|
||||
jsonRes(res, {
|
||||
data: result
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ function simpleChatTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectLLMModel',
|
||||
type: 'selectChatModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
@@ -471,7 +471,7 @@ function datasetTemplate({ formData, maxToken }: Props): ModuleItemType[] {
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectLLMModel',
|
||||
type: 'selectChatModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
|
||||
@@ -88,7 +88,7 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectLLMModel',
|
||||
type: 'selectChatModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
@@ -498,7 +498,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
type: 'selectLLMModel',
|
||||
type: 'selectChatModel',
|
||||
label: 'core.module.input.label.aiModel',
|
||||
required: true,
|
||||
valueType: 'string',
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getLLMModel } from '@/service/core/ai/model';
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
|
||||
const { name, avatar, type, simpleTemplateId, intro, modules, permission } =
|
||||
req.body as AppUpdateParams;
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
@@ -65,7 +65,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
avatar,
|
||||
intro,
|
||||
permission,
|
||||
teamTags: teamTags,
|
||||
...(modules && {
|
||||
modules
|
||||
})
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import type { AppUpdateParams } from '@fastgpt/global/core/app/api';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { getLLMModel } from '@/service/core/ai/model';
|
||||
|
||||
/* 获取我的模型 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { name, avatar, type, simpleTemplateId, intro, modules, permission, teamTags } =
|
||||
req.body as AppUpdateParams;
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
if (!appId) {
|
||||
throw new Error('appId is empty');
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' });
|
||||
|
||||
// check modules
|
||||
// 1. dataset search limit, less than model quoteMaxToken
|
||||
if (modules) {
|
||||
let maxTokens = 3000;
|
||||
|
||||
modules.forEach((item) => {
|
||||
if (item.flowType === FlowNodeTypeEnum.chatNode) {
|
||||
const model =
|
||||
item.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
|
||||
const chatModel = getLLMModel(model);
|
||||
const quoteMaxToken = chatModel.quoteMaxToken || 3000;
|
||||
|
||||
maxTokens = Math.max(maxTokens, quoteMaxToken);
|
||||
}
|
||||
});
|
||||
|
||||
modules.forEach((item) => {
|
||||
if (item.flowType === FlowNodeTypeEnum.datasetSearchNode) {
|
||||
item.inputs.forEach((input) => {
|
||||
if (input.key === ModuleInputKeyEnum.datasetMaxTokens) {
|
||||
const val = input.value as number;
|
||||
if (val > maxTokens) {
|
||||
input.value = maxTokens;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 更新模型
|
||||
await MongoApp.findOneAndUpdate(
|
||||
{
|
||||
_id: appId
|
||||
},
|
||||
{
|
||||
name,
|
||||
type,
|
||||
simpleTemplateId,
|
||||
avatar,
|
||||
intro,
|
||||
permission,
|
||||
teamTags: teamTags,
|
||||
...(modules && {
|
||||
modules
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,13 @@ import { sseErrRes } from '@fastgpt/service/common/response';
|
||||
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { pushChatBill } from '@/service/support/wallet/bill/push';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { dispatchModules } from '@/service/moduleDispatch';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
|
||||
|
||||
export type Props = {
|
||||
history: ChatItemType[];
|
||||
@@ -50,10 +50,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
]);
|
||||
|
||||
// auth balance
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
const user = await getUserAndAuthBalance({
|
||||
tmbId,
|
||||
minBalance: 0
|
||||
});
|
||||
|
||||
/* start process */
|
||||
const { responseData, moduleDispatchBills } = await dispatchModules({
|
||||
const { responseData } = await dispatchModules({
|
||||
res,
|
||||
mode: 'test',
|
||||
teamId,
|
||||
@@ -82,13 +85,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
res.end();
|
||||
|
||||
pushChatUsage({
|
||||
pushChatBill({
|
||||
appName,
|
||||
appId,
|
||||
teamId,
|
||||
tmbId,
|
||||
source: UsageSourceEnum.fastgpt,
|
||||
moduleDispatchBills
|
||||
source: BillSourceEnum.fastgpt,
|
||||
response: responseData
|
||||
});
|
||||
} catch (err: any) {
|
||||
res.status(500);
|
||||
|
||||
@@ -14,13 +14,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await connectToDatabase();
|
||||
const { appId, shareId, outLinkUid } = req.query as ClearHistoriesProps;
|
||||
|
||||
let chatAppId = appId;
|
||||
|
||||
const match = await (async () => {
|
||||
if (shareId && outLinkUid) {
|
||||
const { appId, uid } = await authOutLink({ shareId, outLinkUid });
|
||||
const { uid } = await authOutLink({ shareId, outLinkUid });
|
||||
|
||||
chatAppId = appId;
|
||||
return {
|
||||
shareId,
|
||||
outLinkUid: uid
|
||||
@@ -44,11 +41,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const idList = list.map((item) => item.chatId);
|
||||
|
||||
await MongoChatItem.deleteMany({
|
||||
appId: chatAppId,
|
||||
appId,
|
||||
chatId: { $in: idList }
|
||||
});
|
||||
await MongoChat.deleteMany({
|
||||
appId: chatAppId,
|
||||
appId,
|
||||
chatId: { $in: idList }
|
||||
});
|
||||
|
||||
|
||||
@@ -28,13 +28,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
};
|
||||
}
|
||||
if (appId && outLinkUid) {
|
||||
return {
|
||||
shareId,
|
||||
outLinkUid: outLinkUid,
|
||||
source: ChatSourceEnum.team
|
||||
};
|
||||
}
|
||||
if (appId) {
|
||||
const { tmbId } = await authCert({ req, authToken: true });
|
||||
return {
|
||||
@@ -43,7 +36,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
source: ChatSourceEnum.online
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject('Params are error');
|
||||
})();
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
|
||||
import { text2Speech } from '@fastgpt/service/core/ai/audio/speech';
|
||||
import { pushAudioSpeechUsage } from '@/service/support/wallet/usage/push';
|
||||
import { pushAudioSpeechBill } from '@/service/support/wallet/bill/push';
|
||||
import { authCertOrShareId } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { authType2UsageSource } from '@/service/support/wallet/usage/utils';
|
||||
import { authType2BillSource } from '@/service/support/wallet/bill/utils';
|
||||
import { getAudioSpeechModel } from '@/service/core/ai/model';
|
||||
import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
|
||||
|
||||
@@ -54,12 +54,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
speed: ttsConfig.speed,
|
||||
onSuccess: async ({ model, buffer }) => {
|
||||
try {
|
||||
pushAudioSpeechUsage({
|
||||
pushAudioSpeechBill({
|
||||
model: model,
|
||||
charsLength: input.length,
|
||||
tmbId,
|
||||
teamId,
|
||||
source: authType2UsageSource({ authType })
|
||||
source: authType2BillSource({ authType })
|
||||
});
|
||||
|
||||
await MongoTTSBuffer.create({
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import type { chatByTeamProps } from '@/global/core/chat/api.d';
|
||||
import axios from 'axios';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
||||
import { selectShareResponse } from '@/utils/service/core/chat';
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
let { teamId, appId, outLinkUid } = req.query as chatByTeamProps;
|
||||
|
||||
const history = await MongoChatItem.find({
|
||||
appId: appId,
|
||||
outLinkUid: outLinkUid,
|
||||
teamId: teamId
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: history
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
data: req.query,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
}
|
||||
};
|
||||
@@ -1,91 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { getChatModelNameListByModules } from '@/service/core/app/module';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
||||
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
let { appId, chatId, outLinkUid } = req.query as {
|
||||
chatId?: string;
|
||||
appId?: string;
|
||||
outLinkUid?: string;
|
||||
};
|
||||
|
||||
if (!appId) {
|
||||
return jsonRes(res, {
|
||||
code: 501,
|
||||
message: "You don't have an app yet"
|
||||
});
|
||||
}
|
||||
|
||||
// auth app permission
|
||||
const [chat, app] = await Promise.all([
|
||||
// authApp({
|
||||
// req,
|
||||
// authToken: false,
|
||||
// appId,
|
||||
// per: 'r'
|
||||
// }),
|
||||
chatId ? MongoChat.findOne({ appId, chatId }) : undefined,
|
||||
MongoApp.findById(appId).lean()
|
||||
]);
|
||||
if (!app) {
|
||||
throw new Error(AppErrEnum.unExist);
|
||||
}
|
||||
|
||||
// auth chat permission
|
||||
// if (chat && chat.outLinkUid !== outLinkUid) {
|
||||
// throw new Error(ChatErrEnum.unAuthChat);
|
||||
// }
|
||||
// // auth chat permission
|
||||
// if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
|
||||
// throw new Error(ChatErrEnum.unAuthChat);
|
||||
// }
|
||||
|
||||
// get app and history
|
||||
const { history } = await getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
limit: 30,
|
||||
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${ModuleOutputKeyEnum.responseData}`
|
||||
});
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId,
|
||||
appId,
|
||||
title: chat?.title || '新对话',
|
||||
userAvatar: undefined,
|
||||
variables: chat?.variables || {},
|
||||
history,
|
||||
app: {
|
||||
userGuideModule: getGuideModule(app.modules),
|
||||
chatModels: getChatModelNameListByModules(app.modules),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
}
|
||||
};
|
||||
@@ -1,81 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { getChatModelNameListByModules } from '@/service/core/app/module';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
|
||||
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
||||
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
let { appId, chatId, loadCustomFeedbacks } = req.query as InitChatProps;
|
||||
|
||||
if (!appId) {
|
||||
return jsonRes(res, {
|
||||
code: 501,
|
||||
message: "You don't have an app yet"
|
||||
});
|
||||
}
|
||||
|
||||
// auth app permission
|
||||
const [{ app, tmbId }, chat] = await Promise.all([
|
||||
authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: 'r'
|
||||
}),
|
||||
chatId ? MongoChat.findOne({ appId, chatId }) : undefined
|
||||
]);
|
||||
|
||||
// // auth chat permission
|
||||
// if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
|
||||
// throw new Error(ChatErrEnum.unAuthChat);
|
||||
// }
|
||||
|
||||
// get app and history
|
||||
const { history } = await getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
limit: 30,
|
||||
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${
|
||||
ModuleOutputKeyEnum.responseData
|
||||
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`
|
||||
});
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
data: {
|
||||
chatId,
|
||||
appId,
|
||||
title: chat?.title || '新对话',
|
||||
userAvatar: undefined,
|
||||
variables: chat?.variables || {},
|
||||
history,
|
||||
app: {
|
||||
userGuideModule: getGuideModule(app.modules),
|
||||
chatModels: getChatModelNameListByModules(app.modules),
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
intro: app.intro
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
responseLimit: '10mb'
|
||||
}
|
||||
};
|
||||
@@ -24,7 +24,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
await MongoChat.findOneAndUpdate(
|
||||
{ appId, chatId },
|
||||
{
|
||||
updateTime: new Date(),
|
||||
...(customTitle !== undefined && { customTitle }),
|
||||
...(top !== undefined && { top })
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@ import {
|
||||
TrainingModeEnum,
|
||||
DatasetCollectionTypeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
|
||||
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
|
||||
import { reloadCollectionChunks } from '@fastgpt/service/core/dataset/collection/utils';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
@@ -42,7 +43,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
// 1. check dataset limit
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: predictDataLimitLength(trainingType, new Array(10))
|
||||
insertLen: predictDataLimitLength(trainingType, new Array(10)),
|
||||
standardPlans: getStandardSubPlan()
|
||||
});
|
||||
|
||||
const { _id: collectionId } = await mongoSessionRun(async (session) => {
|
||||
@@ -64,11 +66,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
});
|
||||
|
||||
// 3. create bill and start sync
|
||||
const { billId } = await createTrainingUsage({
|
||||
const { billId } = await createTrainingBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
appName: 'core.dataset.collection.Sync Collection',
|
||||
billSource: UsageSourceEnum.training,
|
||||
billSource: BillSourceEnum.training,
|
||||
vectorModel: getVectorModel(dataset.vectorModel).name,
|
||||
agentModel: getLLMModel(dataset.agentModel).name,
|
||||
session
|
||||
|
||||
@@ -12,13 +12,14 @@ import {
|
||||
DatasetCollectionTypeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
||||
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
|
||||
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
|
||||
import { hashStr } from '@fastgpt/global/common/string/tools';
|
||||
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -52,7 +53,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
// 2. check dataset limit
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: predictDataLimitLength(trainingType, chunks)
|
||||
insertLen: predictDataLimitLength(trainingType, chunks),
|
||||
standardPlans: getStandardSubPlan()
|
||||
});
|
||||
|
||||
// 3. create collection and training bill
|
||||
@@ -72,11 +74,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
hashRawText: hashStr(text),
|
||||
rawTextLength: text.length
|
||||
}),
|
||||
createTrainingUsage({
|
||||
createTrainingBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
appName: name,
|
||||
billSource: UsageSourceEnum.training,
|
||||
billSource: BillSourceEnum.training,
|
||||
vectorModel: getVectorModel(dataset.vectorModel)?.name,
|
||||
agentModel: getLLMModel(dataset.agentModel)?.name
|
||||
})
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
DatasetCollectionTypeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
|
||||
import { createOneCollection } from '@fastgpt/service/core/dataset/collection/controller';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
@@ -56,11 +56,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
// create training bill
|
||||
const { billId } = await createTrainingUsage({
|
||||
const { billId } = await createTrainingBill({
|
||||
teamId: collection.teamId,
|
||||
tmbId,
|
||||
appName: 'core.dataset.collection.Sync Collection',
|
||||
billSource: UsageSourceEnum.training,
|
||||
billSource: BillSourceEnum.training,
|
||||
vectorModel: vectorModelData.name,
|
||||
agentModel: agentModelData.name,
|
||||
session
|
||||
|
||||
@@ -7,7 +7,6 @@ import { createDefaultCollection } from '@fastgpt/service/core/dataset/collectio
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { getLLMModel, getVectorModel, getDatasetModel } from '@/service/core/ai/model';
|
||||
import { checkTeamDatasetLimit } from '@/service/support/permission/teamLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -32,7 +31,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
}
|
||||
|
||||
// check limit
|
||||
await checkTeamDatasetLimit(teamId);
|
||||
const authCount = await MongoDataset.countDocuments({
|
||||
teamId,
|
||||
type: DatasetTypeEnum.dataset
|
||||
});
|
||||
if (authCount >= 50) {
|
||||
throw new Error('每个团队上限 50 个知识库');
|
||||
}
|
||||
|
||||
const { _id } = await MongoDataset.create({
|
||||
name,
|
||||
|
||||
@@ -3,7 +3,8 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authDatasetData } from '@/service/support/permission/auth/dataset';
|
||||
import { deleteDatasetData } from '@/service/core/dataset/data/controller';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/controller';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -25,7 +26,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
per: 'w'
|
||||
});
|
||||
|
||||
await deleteDatasetData(datasetData);
|
||||
// update mongo data update time
|
||||
await MongoDatasetData.findByIdAndUpdate(dataId, {
|
||||
updateTime: new Date()
|
||||
});
|
||||
|
||||
// delete vector data
|
||||
await deleteDatasetDataVector({
|
||||
teamId,
|
||||
idList: datasetData.indexes.map((item) => item.dataId)
|
||||
});
|
||||
|
||||
// delete mongo data
|
||||
await MongoDatasetData.findByIdAndDelete(dataId);
|
||||
|
||||
jsonRes(res, {
|
||||
data: 'success'
|
||||
|
||||
@@ -12,10 +12,12 @@ import { hasSameValue } from '@/service/core/dataset/data/utils';
|
||||
import { insertData2Dataset } from '@/service/core/dataset/data/controller';
|
||||
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
|
||||
import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { InsertOneDatasetDataProps } from '@/global/core/dataset/api';
|
||||
import { simpleText } from '@fastgpt/global/common/string/tools';
|
||||
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -41,7 +43,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: 1
|
||||
insertLen: 1,
|
||||
standardPlans: getStandardSubPlan()
|
||||
});
|
||||
|
||||
// auth collection and get dataset
|
||||
@@ -49,7 +52,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
{
|
||||
datasetId: { _id: datasetId, vectorModel }
|
||||
}
|
||||
] = await Promise.all([getCollectionWithDataset(collectionId)]);
|
||||
] = await Promise.all([getCollectionWithDataset(collectionId), authTeamBalance(teamId)]);
|
||||
|
||||
// format data
|
||||
const formatQ = simpleText(q);
|
||||
@@ -87,7 +90,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
indexes: formatIndexes
|
||||
});
|
||||
|
||||
pushGenerateVectorUsage({
|
||||
pushGenerateVectorBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
charsLength,
|
||||
|
||||
@@ -8,9 +8,10 @@ import type {
|
||||
PushDatasetDataResponse
|
||||
} from '@fastgpt/global/core/dataset/api.d';
|
||||
import { authDatasetCollection } from '@fastgpt/service/support/permission/auth/dataset';
|
||||
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { predictDataLimitLength } from '@fastgpt/global/core/dataset/utils';
|
||||
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -37,7 +38,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
// auth dataset limit
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: predictDataLimitLength(collection.trainingType, data)
|
||||
insertLen: predictDataLimitLength(collection.trainingType, data),
|
||||
standardPlans: getStandardSubPlan()
|
||||
});
|
||||
|
||||
jsonRes<PushDatasetDataResponse>(res, {
|
||||
|
||||
@@ -4,9 +4,9 @@ import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { updateData2Dataset } from '@/service/core/dataset/data/controller';
|
||||
import { authDatasetData } from '@/service/support/permission/auth/dataset';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { UpdateDatasetDataProps } from '@/global/core/dataset/api';
|
||||
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -29,10 +29,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
});
|
||||
|
||||
// auth team balance
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: 1
|
||||
});
|
||||
await authTeamBalance(teamId);
|
||||
|
||||
const { charsLength } = await updateData2Dataset({
|
||||
dataId: id,
|
||||
@@ -42,7 +39,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
model: vectorModel
|
||||
});
|
||||
|
||||
pushGenerateVectorUsage({
|
||||
pushGenerateVectorBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
charsLength,
|
||||
|
||||
@@ -4,16 +4,14 @@ import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||
import type { SearchTestProps, SearchTestResponse } from '@/global/core/dataset/api.d';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { searchDatasetData } from '@/service/core/dataset/data/controller';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { getLLMModel } from '@/service/core/ai/model';
|
||||
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
|
||||
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
|
||||
import {
|
||||
checkTeamAIPoints,
|
||||
checkTeamReRankPermission
|
||||
} from '@/service/support/permission/teamLimit';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -45,7 +43,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
per: 'r'
|
||||
});
|
||||
// auth balance
|
||||
await checkTeamAIPoints(teamId);
|
||||
await authTeamBalance(teamId);
|
||||
|
||||
// query extension
|
||||
const extensionModel =
|
||||
@@ -67,27 +65,28 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
similarity,
|
||||
datasetIds: [datasetId],
|
||||
searchMode,
|
||||
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId))
|
||||
usingReRank
|
||||
});
|
||||
|
||||
// push bill
|
||||
const { totalPoints } = pushGenerateVectorUsage({
|
||||
const { total } = pushGenerateVectorBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
charsLength,
|
||||
model: dataset.vectorModel,
|
||||
source: apikey ? UsageSourceEnum.api : UsageSourceEnum.fastgpt,
|
||||
source: apikey ? BillSourceEnum.api : BillSourceEnum.fastgpt,
|
||||
|
||||
...(aiExtensionResult &&
|
||||
extensionModel && {
|
||||
extensionModel: extensionModel.name,
|
||||
extensionCharsLength: aiExtensionResult.charsLength
|
||||
extensionInputTokens: aiExtensionResult.inputTokens,
|
||||
extensionOutputTokens: aiExtensionResult.outputTokens
|
||||
})
|
||||
});
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
apikey,
|
||||
totalPoints: totalPoints
|
||||
usage: total
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
|
||||
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
|
||||
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
|
||||
import { checkTeamPluginLimit } from '@/service/support/permission/teamLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -12,8 +11,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const { teamId, tmbId } = await authUserNotVisitor({ req, authToken: true });
|
||||
const body = req.body as CreateOnePluginParams;
|
||||
|
||||
await checkTeamPluginLimit(teamId);
|
||||
|
||||
const { _id } = await MongoPlugin.create({
|
||||
...body,
|
||||
teamId,
|
||||
|
||||
@@ -34,6 +34,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
});
|
||||
|
||||
const textResult = replaceVariable(text, obj);
|
||||
|
||||
res.json({
|
||||
text: textResult
|
||||
});
|
||||
|
||||
@@ -5,7 +5,6 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
import { getAIApi, openaiBaseUrl } from '@fastgpt/service/core/ai/config';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
|
||||
/* update user info */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
@@ -13,12 +12,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
await connectToDatabase();
|
||||
const { avatar, timezone, openaiAccount } = req.body as UserUpdateParams;
|
||||
|
||||
const { tmbId } = await authCert({ req, authToken: true });
|
||||
const tmb = await MongoTeamMember.findById(tmbId);
|
||||
if (!tmb) {
|
||||
throw new Error('can not find it');
|
||||
}
|
||||
const userId = tmb.userId;
|
||||
const { userId } = await authCert({ req, authToken: true });
|
||||
|
||||
// auth key
|
||||
if (openaiAccount?.key) {
|
||||
console.log('auth user openai key', openaiAccount?.key);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -14,12 +13,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
throw new Error('Params is missing');
|
||||
}
|
||||
|
||||
const { tmbId } = await authCert({ req, authToken: true });
|
||||
const tmb = await MongoTeamMember.findById(tmbId);
|
||||
if (!tmb) {
|
||||
throw new Error('can not find it');
|
||||
}
|
||||
const userId = tmb.userId;
|
||||
const { userId } = await authCert({ req, authToken: true });
|
||||
|
||||
// auth old password
|
||||
const user = await MongoUser.findOne({
|
||||
_id: userId,
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { checkDatasetLimit } from '@/service/support/permission/teamLimit';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/limit/dataset';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -22,7 +23,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: numberSize
|
||||
insertLen: numberSize,
|
||||
standardPlans: getStandardSubPlan()
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import { CreateTrainingUsageProps } from '@fastgpt/global/support/wallet/usage/api.d';
|
||||
import { BillSourceEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import { CreateTrainingBillProps } from '@fastgpt/global/support/wallet/bill/api.d';
|
||||
import { getLLMModel, getVectorModel } from '@/service/core/ai/model';
|
||||
import { createTrainingUsage } from '@fastgpt/service/support/wallet/usage/controller';
|
||||
import { createTrainingBill } from '@fastgpt/service/support/wallet/bill/controller';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/auth/dataset';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { name, datasetId } = req.body as CreateTrainingUsageProps;
|
||||
const { name, datasetId } = req.body as CreateTrainingBillProps;
|
||||
|
||||
const { teamId, tmbId, dataset } = await authDataset({
|
||||
req,
|
||||
@@ -20,11 +20,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
per: 'w'
|
||||
});
|
||||
|
||||
const { billId } = await createTrainingUsage({
|
||||
const { billId } = await createTrainingBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
appName: name,
|
||||
billSource: UsageSourceEnum.training,
|
||||
billSource: BillSourceEnum.training,
|
||||
vectorModel: getVectorModel(dataset.vectorModel).name,
|
||||
agentModel: getLLMModel(dataset.agentModel).name
|
||||
});
|
||||
@@ -2,21 +2,22 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { getTeamSubPlans } from '@fastgpt/service/support/wallet/sub/utils';
|
||||
import { getTeamSubPlanStatus } from '@fastgpt/service/support/wallet/sub/utils';
|
||||
import { getStandardSubPlan } from '@/service/support/wallet/sub/utils';
|
||||
import { FeTeamPlanStatusType } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { FeTeamSubType } from '@fastgpt/global/support/wallet/sub/type';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
// 凭证校验
|
||||
const { teamId } = await authCert({
|
||||
req,
|
||||
authToken: true
|
||||
});
|
||||
|
||||
jsonRes<FeTeamPlanStatusType>(res, {
|
||||
data: await getTeamSubPlans({
|
||||
jsonRes<FeTeamSubType>(res, {
|
||||
data: await getTeamSubPlanStatus({
|
||||
teamId,
|
||||
standardPlans: getStandardSubPlan()
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ import { getUploadModel } from '@fastgpt/service/common/file/multer';
|
||||
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
|
||||
import fs from 'fs';
|
||||
import { getAIApi } from '@fastgpt/service/core/ai/config';
|
||||
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
|
||||
import { pushWhisperBill } from '@/service/support/wallet/bill/push';
|
||||
|
||||
const upload = getUploadModel({
|
||||
maxSize: 2
|
||||
@@ -40,7 +40,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
model: global.whisperModel.model
|
||||
});
|
||||
|
||||
pushWhisperUsage({
|
||||
pushWhisperBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
duration
|
||||
|
||||
@@ -13,16 +13,16 @@ import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt';
|
||||
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
||||
import { saveChat } from '@/service/utils/chat/saveChat';
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { pushChatBill } from '@/service/support/wallet/bill/push';
|
||||
import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink';
|
||||
import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
|
||||
import { pushResult2Remote, updateOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
|
||||
import requestIp from 'request-ip';
|
||||
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
|
||||
import { selectShareResponse } from '@/utils/service/core/chat';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||
import { getUserAndAuthBalance } from '@fastgpt/service/support/user/controller';
|
||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { autChatCrud } from '@/service/support/permission/auth/chat';
|
||||
@@ -35,14 +35,9 @@ type FastGptShareChatProps = {
|
||||
shareId?: string;
|
||||
outLinkUid?: string;
|
||||
};
|
||||
type FastGptTeamShareChatProps = {
|
||||
teamId?: string;
|
||||
outLinkUid?: string;
|
||||
};
|
||||
export type Props = ChatCompletionCreateParams &
|
||||
FastGptWebChatProps &
|
||||
FastGptShareChatProps &
|
||||
FastGptTeamShareChatProps & {
|
||||
FastGptShareChatProps & {
|
||||
messages: ChatMessageItemType[];
|
||||
stream?: boolean;
|
||||
detail?: boolean;
|
||||
@@ -65,7 +60,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
const {
|
||||
chatId,
|
||||
appId,
|
||||
teamId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
stream = false,
|
||||
@@ -103,96 +97,90 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
}
|
||||
|
||||
/* auth app permission */
|
||||
const { teamId, tmbId, user, app, responseDetail, authType, apikey, canWrite, outLinkUserId } =
|
||||
await (async () => {
|
||||
if (shareId && outLinkUid) {
|
||||
const { teamId, tmbId, user, appId, authType, responseDetail, uid } =
|
||||
await authOutLinkChatStart({
|
||||
shareId,
|
||||
ip: originIp,
|
||||
outLinkUid,
|
||||
question: question.value
|
||||
});
|
||||
const app = await MongoApp.findById(appId);
|
||||
|
||||
if (!app) {
|
||||
return Promise.reject('app is empty');
|
||||
}
|
||||
|
||||
return {
|
||||
teamId,
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail,
|
||||
apikey: '',
|
||||
authType,
|
||||
canWrite: false,
|
||||
outLinkUserId: uid
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
appId: apiKeyAppId,
|
||||
teamId,
|
||||
tmbId,
|
||||
authType,
|
||||
apikey
|
||||
} = await authCert({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
const { user, app, responseDetail, authType, apikey, canWrite, uid } = await (async () => {
|
||||
if (shareId && outLinkUid) {
|
||||
const { user, appId, authType, responseDetail, uid } = await authOutLinkChatStart({
|
||||
shareId,
|
||||
ip: originIp,
|
||||
outLinkUid,
|
||||
question: question.value
|
||||
});
|
||||
const app = await MongoApp.findById(appId);
|
||||
|
||||
const user = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
|
||||
// openapi key
|
||||
if (authType === AuthUserTypeEnum.apikey) {
|
||||
if (!apiKeyAppId) {
|
||||
return Promise.reject(
|
||||
'Key is error. You need to use the app key rather than the account key.'
|
||||
);
|
||||
}
|
||||
const app = await MongoApp.findById(apiKeyAppId);
|
||||
|
||||
if (!app) {
|
||||
return Promise.reject('app is empty');
|
||||
}
|
||||
|
||||
return {
|
||||
teamId,
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail: detail,
|
||||
apikey,
|
||||
authType,
|
||||
canWrite: true
|
||||
};
|
||||
if (!app) {
|
||||
return Promise.reject('app is empty');
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
app,
|
||||
responseDetail,
|
||||
apikey: '',
|
||||
authType,
|
||||
canWrite: false,
|
||||
uid
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
appId: apiKeyAppId,
|
||||
tmbId,
|
||||
authType,
|
||||
apikey
|
||||
} = await authCert({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true
|
||||
});
|
||||
|
||||
const user = await getUserAndAuthBalance({
|
||||
tmbId,
|
||||
minBalance: 0
|
||||
});
|
||||
|
||||
// openapi key
|
||||
if (authType === AuthUserTypeEnum.apikey) {
|
||||
if (!apiKeyAppId) {
|
||||
return Promise.reject(
|
||||
'Key is error. You need to use the app key rather than the account key.'
|
||||
);
|
||||
}
|
||||
const app = await MongoApp.findById(apiKeyAppId);
|
||||
|
||||
if (!app) {
|
||||
return Promise.reject('app is empty');
|
||||
}
|
||||
|
||||
// token auth
|
||||
if (!appId) {
|
||||
return Promise.reject('appId is empty');
|
||||
}
|
||||
const { app, canWrite } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: 'r'
|
||||
});
|
||||
|
||||
return {
|
||||
teamId,
|
||||
tmbId,
|
||||
user,
|
||||
app,
|
||||
responseDetail: detail,
|
||||
apikey,
|
||||
authType,
|
||||
canWrite: canWrite || false
|
||||
canWrite: true
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
// token auth
|
||||
if (!appId) {
|
||||
return Promise.reject('appId is empty');
|
||||
}
|
||||
const { app, canWrite } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: 'r'
|
||||
});
|
||||
|
||||
return {
|
||||
user,
|
||||
app,
|
||||
responseDetail: detail,
|
||||
apikey,
|
||||
authType,
|
||||
canWrite: canWrite || false
|
||||
};
|
||||
})();
|
||||
|
||||
// auth chat permission
|
||||
await autChatCrud({
|
||||
@@ -213,17 +201,16 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
limit: 30,
|
||||
field: `dataId obj value`
|
||||
});
|
||||
|
||||
const concatHistories = history.concat(chatMessages);
|
||||
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
|
||||
|
||||
/* start flow controller */
|
||||
const { responseData, moduleDispatchBills, answerText } = await dispatchModules({
|
||||
const { responseData, answerText } = await dispatchModules({
|
||||
res,
|
||||
mode: 'chat',
|
||||
user,
|
||||
teamId: String(teamId),
|
||||
tmbId: String(tmbId),
|
||||
teamId: String(user.team.teamId),
|
||||
tmbId: String(user.team.tmbId),
|
||||
appId: String(app._id),
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
@@ -236,19 +223,18 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
stream,
|
||||
detail
|
||||
});
|
||||
console.log('af');
|
||||
|
||||
// save chat
|
||||
if (chatId) {
|
||||
await saveChat({
|
||||
chatId,
|
||||
appId: app._id,
|
||||
teamId: teamId,
|
||||
tmbId: tmbId,
|
||||
teamId: user.team.teamId,
|
||||
tmbId: user.team.tmbId,
|
||||
variables,
|
||||
updateUseTime: !shareId && String(tmbId) === String(app.tmbId), // owner update use time
|
||||
updateUseTime: !shareId && String(user.team.tmbId) === String(app.tmbId), // owner update use time
|
||||
shareId,
|
||||
outLinkUid: outLinkUserId,
|
||||
outLinkUid: uid,
|
||||
source: (() => {
|
||||
if (shareId) {
|
||||
return ChatSourceEnum.share;
|
||||
@@ -319,29 +305,29 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
}
|
||||
|
||||
// add record
|
||||
const { totalPoints } = pushChatUsage({
|
||||
const { total } = pushChatBill({
|
||||
appName: app.name,
|
||||
appId: app._id,
|
||||
teamId: teamId,
|
||||
tmbId: tmbId,
|
||||
source: getUsageSourceByAuthType({ shareId, authType }),
|
||||
moduleDispatchBills
|
||||
teamId: user.team.teamId,
|
||||
tmbId: user.team.tmbId,
|
||||
source: getBillSourceByAuthType({ shareId, authType }),
|
||||
response: responseData
|
||||
});
|
||||
|
||||
if (shareId) {
|
||||
pushResult2Remote({ outLinkUid, shareId, appName: app.name, responseData });
|
||||
addOutLinkUsage({
|
||||
pushResult2Remote({ outLinkUid, shareId, responseData });
|
||||
updateOutLinkUsage({
|
||||
shareId,
|
||||
totalPoints
|
||||
total
|
||||
});
|
||||
}
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
apikey,
|
||||
totalPoints
|
||||
usage: total
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
if (stream) {
|
||||
sseErrRes(res, err);
|
||||
res.end();
|
||||
|
||||
@@ -2,13 +2,13 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { getVectorsByText } from '@fastgpt/service/core/ai/embedding';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { getBillSourceByAuthType } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { getVectorModel } from '@/service/core/ai/model';
|
||||
import { checkTeamAIPoints } from '@/service/support/permission/teamLimit';
|
||||
|
||||
type Props = {
|
||||
input: string | string[];
|
||||
@@ -34,7 +34,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
authApiKey: true
|
||||
});
|
||||
|
||||
await checkTeamAIPoints(teamId);
|
||||
await authTeamBalance(teamId);
|
||||
|
||||
const { charsLength, vectors } = await getVectorsByText({
|
||||
input: query,
|
||||
@@ -55,19 +55,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
}
|
||||
});
|
||||
|
||||
const { totalPoints } = pushGenerateVectorUsage({
|
||||
const { total } = pushGenerateVectorBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
charsLength,
|
||||
model,
|
||||
billId,
|
||||
source: getUsageSourceByAuthType({ authType })
|
||||
source: getBillSourceByAuthType({ authType })
|
||||
});
|
||||
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
apikey,
|
||||
totalPoints: totalPoints
|
||||
usage: total
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
50
projects/app/src/pages/api/v1/rerank.ts
Normal file
50
projects/app/src/pages/api/v1/rerank.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
||||
import { pushReRankBill } from '@/service/support/wallet/bill/push';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { PostReRankProps, PostReRankResponse } from '@fastgpt/global/core/ai/api';
|
||||
import { reRankRecall } from '@/service/core/ai/rerank';
|
||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
let { query, inputs } = req.body as PostReRankProps;
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { teamId, tmbId, apikey } = await authCert({
|
||||
req,
|
||||
authApiKey: true
|
||||
});
|
||||
await authTeamBalance(teamId);
|
||||
|
||||
// max 150 length
|
||||
inputs = inputs.slice(0, 150);
|
||||
|
||||
const result = await reRankRecall({ query, inputs });
|
||||
|
||||
const { total } = pushReRankBill({
|
||||
teamId,
|
||||
tmbId,
|
||||
source: 'api',
|
||||
inputs
|
||||
});
|
||||
|
||||
if (apikey) {
|
||||
updateApiKeyUsage({
|
||||
apikey,
|
||||
usage: total
|
||||
});
|
||||
}
|
||||
|
||||
jsonRes<PostReRankResponse>(res, {
|
||||
data: result
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,201 @@
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getAppTotalUsage } from '@/web/core/app/api';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import dayjs from 'dayjs';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import Loading from '@/components/Loading';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
const map = {
|
||||
blue: {
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(3, 190, 232, 0.42)' // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(0, 182, 240, 0)'
|
||||
}
|
||||
],
|
||||
global: false // 缺省为 false
|
||||
},
|
||||
lineColor: '#36ADEF'
|
||||
},
|
||||
deepBlue: {
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(47, 112, 237, 0.42)' // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(94, 159, 235, 0)'
|
||||
}
|
||||
],
|
||||
global: false
|
||||
},
|
||||
lineColor: '#3293EC'
|
||||
},
|
||||
purple: {
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(211, 190, 255, 0.42)' // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(52, 60, 255, 0)'
|
||||
}
|
||||
],
|
||||
global: false // 缺省为 false
|
||||
},
|
||||
lineColor: '#8172D8'
|
||||
},
|
||||
green: {
|
||||
backgroundColor: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(4, 209, 148, 0.42)' // 0% 处的颜色
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(19, 217, 181, 0)'
|
||||
}
|
||||
],
|
||||
global: false // 缺省为 false
|
||||
},
|
||||
lineColor: '#00A9A6',
|
||||
max: 100
|
||||
}
|
||||
};
|
||||
|
||||
const TokenUsage = ({ appId }: { appId: string }) => {
|
||||
const { screenWidth } = useSystemStore();
|
||||
|
||||
const Dom = useRef<HTMLDivElement>(null);
|
||||
const myChart = useRef<echarts.ECharts>();
|
||||
const { data = [] } = useQuery(['init'], () => getAppTotalUsage({ appId }));
|
||||
|
||||
const option = useMemo(
|
||||
() => ({
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
show: false,
|
||||
boundaryGap: false,
|
||||
data: data.map((item) => item.date)
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
splitNumber: 3,
|
||||
min: 0
|
||||
},
|
||||
grid: {
|
||||
show: false,
|
||||
left: 5,
|
||||
right: 5,
|
||||
top: 0,
|
||||
bottom: 5
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'line'
|
||||
},
|
||||
formatter: (e: any[]) => {
|
||||
const data = e[0];
|
||||
if (!data) return '';
|
||||
|
||||
return `
|
||||
<div>
|
||||
<div>${dayjs(data.axisValue).format('YYYY/MM/DD')}</div>
|
||||
<div>${formatStorePrice2Read(e[0]?.value || 0)}元</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: data.map((item) => item.total),
|
||||
type: 'line',
|
||||
showSymbol: true,
|
||||
animationDuration: 1000,
|
||||
animationEasingUpdate: 'linear',
|
||||
areaStyle: {
|
||||
color: map['blue'].backgroundColor
|
||||
},
|
||||
lineStyle: {
|
||||
width: '1',
|
||||
color: map['blue'].lineColor
|
||||
},
|
||||
itemStyle: {
|
||||
width: 1.5,
|
||||
color: map['blue'].lineColor
|
||||
},
|
||||
emphasis: {
|
||||
// highlight
|
||||
disabled: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}),
|
||||
[data]
|
||||
);
|
||||
|
||||
// init chart
|
||||
useEffect(() => {
|
||||
if (!Dom.current || myChart?.current?.getOption()) return;
|
||||
myChart.current = echarts.init(Dom.current);
|
||||
myChart.current && myChart.current.setOption(option);
|
||||
|
||||
setTimeout(() => {
|
||||
myChart.current?.resize();
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
// data changed, update
|
||||
useEffect(() => {
|
||||
if (!myChart.current || !myChart?.current?.getOption()) return;
|
||||
myChart.current.setOption(option);
|
||||
}, [data, option]);
|
||||
|
||||
// resize chart
|
||||
useEffect(() => {
|
||||
if (!myChart.current || !myChart.current.getOption()) return;
|
||||
myChart.current.resize();
|
||||
}, [screenWidth]);
|
||||
|
||||
return (
|
||||
<Box ref={Dom} w={'100%'} flex={'1 0 0'} h={'100%'} position={'relative'}>
|
||||
<Loading fixed={false} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TokenUsage);
|
||||
@@ -36,6 +36,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { defaultOutLinkForm } from '@/constants/app';
|
||||
import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
@@ -93,7 +94,7 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('common.Name')}</Th>
|
||||
<Th>{t('support.outlink.Usage points')}</Th>
|
||||
<Th>{t('common.Price used')}</Th>
|
||||
<Th>{t('core.app.share.Is response quote')}</Th>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
@@ -111,11 +112,11 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
<Tr key={item._id}>
|
||||
<Td>{item.name}</Td>
|
||||
<Td>
|
||||
{Math.round(item.usagePoints)}
|
||||
{formatStorePrice2Read(item.total)}
|
||||
{feConfigs?.isPlus
|
||||
? `${
|
||||
item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1
|
||||
? ` / ${item.limit.maxUsagePoints}`
|
||||
item.limit && item.limit.credit > -1
|
||||
? ` / ¥${item.limit.credit}`
|
||||
: ` / ${t('common.Unlimited')}`
|
||||
}`
|
||||
: ''}
|
||||
@@ -314,15 +315,15 @@ function EditLinkModal({
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
{t('support.outlink.Max usage points')}
|
||||
<MyTooltip label={t('support.outlink.Max usage points tip')}>
|
||||
{t('common.Max credit')}
|
||||
<MyTooltip label={t('common.Max credit tips' || '')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Input
|
||||
{...register('limit.maxUsagePoints', {
|
||||
{...register('limit.credit', {
|
||||
min: -1,
|
||||
max: 10000000,
|
||||
max: 1000,
|
||||
valueAsNumber: true,
|
||||
required: true
|
||||
})}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Flex, Button, IconButton } from '@chakra-ui/react';
|
||||
import { DragHandleIcon } from '@chakra-ui/icons';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -13,7 +12,6 @@ import PermissionIconText from '@/components/support/permission/IconText';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import TagsEditModal from './tagsEditModal';
|
||||
const InfoModal = dynamic(() => import('../InfoModal'));
|
||||
|
||||
const AppCard = ({ appId }: { appId: string }) => {
|
||||
@@ -22,7 +20,6 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
const { toast } = useToast();
|
||||
const { appDetail } = useAppStore();
|
||||
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
|
||||
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();
|
||||
|
||||
const { openConfirm: openConfirmDel, ConfirmModal: ConfirmDelModal } = useConfirm({
|
||||
content: t('app.Confirm Del App Tip')
|
||||
@@ -126,17 +123,6 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
>
|
||||
{t('core.app.navbar.Publish')}
|
||||
</Button>
|
||||
{appDetail.isOwner && (
|
||||
<Button
|
||||
mr={3}
|
||||
size={['sm', 'md']}
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<DragHandleIcon w={'16px'} />}
|
||||
onClick={() => setTeamTagsSet(appDetail)}
|
||||
>
|
||||
{t('common.Team Tags Set')}
|
||||
</Button>
|
||||
)}
|
||||
{appDetail.isOwner && (
|
||||
<Button
|
||||
size={['sm', 'md']}
|
||||
@@ -150,13 +136,11 @@ const AppCard = ({ appId }: { appId: string }) => {
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ConfirmDelModal />
|
||||
{settingAppInfo && (
|
||||
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
|
||||
)}
|
||||
{TeamTagsSet && (
|
||||
<TagsEditModal appDetail={appDetail} onClose={() => setTeamTagsSet(undefined)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Button, Flex, Box, ModalFooter, ModalBody } from '@chakra-ui/react';
|
||||
import TagsEdit from '@/components/TagEdit';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import { TeamTagsSchema } from '@fastgpt/global/support/user/team/type';
|
||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { getTeamsTags } from '@/web/support/user/team/api';
|
||||
const TagsEditModal = ({ appDetail, onClose }: { appDetail?: any; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [teamsTags, setTeamTags] = useState<Array<TeamTagsSchema>>([]);
|
||||
const [selectedTags, setSelectedTags] = useState(appDetail?.teamTags);
|
||||
const { toast } = useToast();
|
||||
const { replaceAppDetail } = useAppStore();
|
||||
|
||||
// submit config
|
||||
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
|
||||
mutationFn: async () => {
|
||||
await replaceAppDetail(appDetail._id, {
|
||||
teamTags: selectedTags
|
||||
});
|
||||
},
|
||||
onSuccess() {
|
||||
onClose();
|
||||
toast({
|
||||
title: t('common.Update Success'),
|
||||
status: 'success'
|
||||
});
|
||||
},
|
||||
errorToast: t('common.Update Failed')
|
||||
});
|
||||
//
|
||||
|
||||
// // 点击选择标签
|
||||
// const clickTag = (tagId :Number) => {
|
||||
// const index = selectedTags.indexOf(tagId);
|
||||
// if (index === -1) {
|
||||
// // 如果 num 不在数组 arr 中,添加它
|
||||
// setSelectedTags([tagId,...selectedTags])
|
||||
// } else {
|
||||
// const _selectedTags = [...selectedTags];
|
||||
// _selectedTags.splice(index, 1);
|
||||
// console.log('_selectedTags',_selectedTags);
|
||||
// // 如果 num 已经在数组 arr 中,移除它
|
||||
// setSelectedTags(_selectedTags);
|
||||
// }
|
||||
// }
|
||||
|
||||
useEffect(() => {
|
||||
// get team tags
|
||||
getTeamsTags(appDetail?.teamId).then((res: any) => {
|
||||
setTeamTags(res?.list);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
style={{ width: '900px' }}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="/imgs/module/ai.svg"
|
||||
title={'标签管理'}
|
||||
>
|
||||
<ModalBody>
|
||||
{/* <HStack spacing={2}>
|
||||
{teamsTags.map((item,index) => {
|
||||
return <Tag
|
||||
key={index}
|
||||
size={'md'}
|
||||
variant='outline'
|
||||
colorScheme={selectedTags.indexOf(item._id) > -1 ? 'green':'blue' }
|
||||
onClick={() => clickTag(item._id)}
|
||||
>
|
||||
{item.label}
|
||||
</Tag>
|
||||
})}
|
||||
</HStack> */}
|
||||
<Flex width={'100%'} alignItems={'center'}>
|
||||
<Box mb={3} mr={3} fontWeight="semibold">
|
||||
{t('团队标签')}
|
||||
</Box>
|
||||
<TagsEdit
|
||||
defaultValues={selectedTags}
|
||||
teamsTags={teamsTags}
|
||||
setSelectedTags={(item: Array<string>) => setSelectedTags(item)}
|
||||
/>
|
||||
</Flex>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button isLoading={btnLoading} onClick={(e) => saveSubmitSuccess(e)}>
|
||||
{t('common.Save')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
export default TagsEditModal;
|
||||
@@ -8,12 +8,8 @@ import {
|
||||
Input,
|
||||
Grid,
|
||||
useTheme,
|
||||
Card,
|
||||
Text,
|
||||
HStack,
|
||||
Tag
|
||||
Card
|
||||
} from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Grid, Flex, IconButton, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
@@ -8,6 +8,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import Avatar from '@/components/Avatar';
|
||||
@@ -23,7 +24,6 @@ const MyApps = () => {
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
const { myApps, loadMyApps } = useAppStore();
|
||||
const [teamsTags, setTeamTags] = useState([]);
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
title: '删除提示',
|
||||
content: '确认删除该应用所有信息?'
|
||||
@@ -65,9 +65,11 @@ const MyApps = () => {
|
||||
<Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}>
|
||||
{t('app.My Apps')}
|
||||
</Box>
|
||||
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
|
||||
{t('common.New Create')}
|
||||
</Button>
|
||||
{userInfo?.team?.canWrite && (
|
||||
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
|
||||
{t('common.New Create')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Grid
|
||||
py={[4, 6]}
|
||||
@@ -169,10 +171,6 @@ const MyApps = () => {
|
||||
</MyTooltip>
|
||||
))}
|
||||
</Grid>
|
||||
{/* (
|
||||
<ShareBox></ShareBox>
|
||||
) */}
|
||||
|
||||
{myApps.length === 0 && (
|
||||
<Flex mt={'35vh'} flexDirection={'column'} alignItems={'center'}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
|
||||
@@ -1,521 +0,0 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import Head from 'next/head';
|
||||
import { getTeamChatInfo } from '@/web/core/chat/api';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
useDisclosure,
|
||||
Drawer,
|
||||
DrawerOverlay,
|
||||
DrawerContent,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import SideBar from '@/components/SideBar';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import { getChatListById } from '@/web/core/chat/api';
|
||||
import ChatHistorySlider from './components/ChatHistorySlider';
|
||||
import ChatHeader from './components/ChatHeader';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import { useTeamShareChatStore } from '@/web/core/chat/storeTeamChat';
|
||||
import type {
|
||||
ChatHistoryItemType,
|
||||
chatAppListSchema,
|
||||
teamInfoType
|
||||
} from '@fastgpt/global/core/chat/type.d';
|
||||
import { chatContentReplaceBlock } from '@fastgpt/global/core/chat/utils';
|
||||
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { POST } from '@/web/common/api/request';
|
||||
const OutLink = ({
|
||||
teamId,
|
||||
appId,
|
||||
chatId,
|
||||
authToken
|
||||
}: {
|
||||
teamId: string;
|
||||
appId: string;
|
||||
chatId: string;
|
||||
authToken: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const theme = useTheme();
|
||||
const [myApps, setMyApps] = useState<Array<any>>([]);
|
||||
const { isPc } = useSystemStore();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const [teamInfo, setTeamInfo] = useState<teamInfoType>();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const forbidRefresh = useRef(false);
|
||||
|
||||
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
|
||||
const {
|
||||
histories,
|
||||
loadHistories,
|
||||
lastChatAppId,
|
||||
setLastChatAppId,
|
||||
lastChatId,
|
||||
setLastChatId,
|
||||
pushHistory,
|
||||
updateHistory,
|
||||
delOneHistory,
|
||||
chatData,
|
||||
setChatData,
|
||||
delOneHistoryItem,
|
||||
clearHistories
|
||||
} = useChatStore();
|
||||
const {
|
||||
localUId,
|
||||
teamShareChatHistory, // abandon
|
||||
clearLocalHistory // abandon
|
||||
} = useTeamShareChatStore();
|
||||
|
||||
const outLinkUid: string = authToken || localUId;
|
||||
|
||||
// 纯网络获取流程
|
||||
const loadApps = useCallback(async () => {
|
||||
try {
|
||||
if (!teamId) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 根据teamId 获取教研token以及用户tags,然后通过是否为
|
||||
// 根据获取历史记录列表
|
||||
const res = await getChatListById({ teamId, authToken });
|
||||
const { apps = [], teamInfo } = res;
|
||||
setMyApps(apps);
|
||||
setTeamInfo(teamInfo);
|
||||
if (apps.length <= 0) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
}
|
||||
//
|
||||
return null;
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: error?.message
|
||||
});
|
||||
}
|
||||
}, [outLinkUid, router, t, toast]);
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
console.log('res', 13);
|
||||
const prompts = messages.slice(-2);
|
||||
const completionChatId = chatId ? chatId : nanoid();
|
||||
|
||||
const { responseText, responseData } = await streamFetch({
|
||||
data: {
|
||||
messages: prompts,
|
||||
variables,
|
||||
appId,
|
||||
teamId: teamId,
|
||||
outLinkUid: outLinkUid,
|
||||
chatId: completionChatId
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
abortCtrl: controller
|
||||
});
|
||||
|
||||
const newTitle =
|
||||
chatContentReplaceBlock(prompts[0].content).slice(0, 20) ||
|
||||
prompts[1]?.value?.slice(0, 20) ||
|
||||
t('core.chat.New Chat');
|
||||
|
||||
// new chat
|
||||
if (completionChatId !== chatId) {
|
||||
const newHistory: ChatHistoryItemType = {
|
||||
chatId: completionChatId,
|
||||
updateTime: new Date(),
|
||||
title: newTitle,
|
||||
appId,
|
||||
top: false
|
||||
};
|
||||
pushHistory(newHistory);
|
||||
if (controller.signal.reason !== 'leave') {
|
||||
forbidRefresh.current = true;
|
||||
router.replace({
|
||||
query: {
|
||||
chatId: completionChatId,
|
||||
appId,
|
||||
teamId: teamId,
|
||||
authToken: authToken
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// update chat
|
||||
const currentChat = histories.find((item) => item.chatId === chatId);
|
||||
currentChat &&
|
||||
updateHistory({
|
||||
...currentChat,
|
||||
updateTime: new Date(),
|
||||
title: newTitle
|
||||
});
|
||||
}
|
||||
// update chat window
|
||||
setChatData((state) => ({
|
||||
...state,
|
||||
title: newTitle,
|
||||
history: ChatBoxRef.current?.getChatHistories() || state.history
|
||||
}));
|
||||
|
||||
return { responseText, responseData, isNewChat: forbidRefresh.current };
|
||||
},
|
||||
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
|
||||
);
|
||||
|
||||
const { isFetching } = useQuery(['init', appId, teamId], async () => {
|
||||
console.log('res', 3);
|
||||
if (!teamId) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
return;
|
||||
}
|
||||
return teamId && loadApps();
|
||||
});
|
||||
|
||||
useQuery(['loadHistories', appId], () => {
|
||||
console.log('res', 1);
|
||||
teamId && appId ? loadHistories({ appId, outLinkUid }) : null;
|
||||
});
|
||||
// 初始化聊天框
|
||||
useQuery(['init', { appId, chatId }], () => {
|
||||
if (!teamId) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
return;
|
||||
}
|
||||
// pc: redirect to latest model chat
|
||||
if (!appId && lastChatAppId) {
|
||||
return router.replace({
|
||||
query: {
|
||||
appId: lastChatAppId,
|
||||
chatId: lastChatId,
|
||||
teamId: teamId,
|
||||
authToken: authToken
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!appId && myApps[0]) {
|
||||
return router.replace({
|
||||
query: {
|
||||
appId: myApps[0]._id,
|
||||
chatId: lastChatId,
|
||||
teamId: teamId,
|
||||
authToken: authToken
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!appId) {
|
||||
(async () => {
|
||||
const { apps = [] } = await getChatListById({ teamId, authToken });
|
||||
setMyApps(apps);
|
||||
if (apps.length === 0) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
appId: apps[0]._id,
|
||||
chatId: lastChatId,
|
||||
teamId: teamId,
|
||||
authToken: authToken
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
return;
|
||||
}
|
||||
|
||||
// store id
|
||||
appId && setLastChatAppId(appId);
|
||||
setLastChatId(chatId);
|
||||
return loadChatInfo({
|
||||
appId,
|
||||
chatId,
|
||||
loading: appId !== chatData.appId
|
||||
});
|
||||
});
|
||||
|
||||
// get chat app info
|
||||
const loadChatInfo = useCallback(
|
||||
async ({
|
||||
appId,
|
||||
chatId,
|
||||
loading = false
|
||||
}: {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
loading?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
if (!teamId) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
return;
|
||||
}
|
||||
loading && setIsLoading(true);
|
||||
const res = await getTeamChatInfo({ appId, chatId, outLinkUid });
|
||||
console.log('res', res);
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
|
||||
setChatData({
|
||||
...res,
|
||||
history
|
||||
});
|
||||
|
||||
// have records.
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
if (res.history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
// reset all chat tore
|
||||
setLastChatAppId('');
|
||||
setLastChatId('');
|
||||
toast({
|
||||
title: t('core.chat.Failed to initialize chat'),
|
||||
status: 'error'
|
||||
});
|
||||
if (e?.code === 501) {
|
||||
//router.replace('/app/list');
|
||||
} else if (chatId) {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: ''
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
return null;
|
||||
},
|
||||
[setIsLoading, setChatData, router, setLastChatAppId, setLastChatId, toast]
|
||||
);
|
||||
// 监测路由改变
|
||||
useEffect(() => {
|
||||
const activeHistory = teamShareChatHistory.filter((item) => !item.delete);
|
||||
if (!localUId || !teamId || activeHistory.length === 0) return;
|
||||
(async () => {
|
||||
try {
|
||||
await POST('/core/chat/initLocalShareHistoryV464', {
|
||||
outLinkUid: localUId,
|
||||
chatIds: teamShareChatHistory.map((item) => item.chatId)
|
||||
});
|
||||
clearLocalHistory();
|
||||
// router.reload();
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('core.shareChat.Init Error')
|
||||
});
|
||||
}
|
||||
})();
|
||||
}, [clearLocalHistory, localUId, router, teamShareChatHistory, teamId, t, toast]);
|
||||
|
||||
return (
|
||||
<Flex h={'100%'}>
|
||||
{/* pc show myself apps */}
|
||||
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
|
||||
<Flex flexDirection={'column'} h={'100%'}>
|
||||
<Box flex={'1 0 0'} h={0} px={5} py={4} overflow={'overlay'}>
|
||||
{myApps.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
py={2}
|
||||
px={3}
|
||||
mb={3}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
alignItems={'center'}
|
||||
{...(item._id === appId
|
||||
? {
|
||||
bg: 'white',
|
||||
boxShadow: 'md'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
bg: 'myGray.200'
|
||||
},
|
||||
onClick: () => {
|
||||
router.replace({
|
||||
query: {
|
||||
appId: item._id,
|
||||
teamId: teamId,
|
||||
authToken: authToken
|
||||
}
|
||||
});
|
||||
}
|
||||
})}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'24px'} />
|
||||
<Box ml={2} className={'textEllipsis'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
|
||||
{((children: React.ReactNode) => {
|
||||
return isPc || !appId ? (
|
||||
<SideBar>{children}</SideBar>
|
||||
) : (
|
||||
<Drawer
|
||||
isOpen={isOpenSlider}
|
||||
placement="left"
|
||||
autoFocus={false}
|
||||
size={'xs'}
|
||||
onClose={onCloseSlider}
|
||||
>
|
||||
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
|
||||
<DrawerContent maxWidth={'250px'}>{children}</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
})(
|
||||
<ChatHistorySlider
|
||||
appId={appId}
|
||||
appName={chatData.app.name}
|
||||
appAvatar={chatData.app.avatar}
|
||||
activeChatId={chatId}
|
||||
onClose={onCloseSlider}
|
||||
history={histories.map((item, i) => ({
|
||||
id: item.chatId,
|
||||
title: item.title,
|
||||
customTitle: item.customTitle,
|
||||
top: item.top
|
||||
}))}
|
||||
onChangeChat={(chatId) => {
|
||||
router.replace({
|
||||
query: {
|
||||
chatId: chatId || '',
|
||||
appId,
|
||||
teamId: teamId,
|
||||
authToken: authToken
|
||||
}
|
||||
});
|
||||
if (!isPc) {
|
||||
onCloseSlider();
|
||||
}
|
||||
}}
|
||||
onDelHistory={(e) => delOneHistory({ ...e, appId })}
|
||||
onClearHistory={() => {
|
||||
clearHistories({ appId });
|
||||
router.replace({
|
||||
query: {
|
||||
appId,
|
||||
teamId: teamId,
|
||||
authToken: authToken
|
||||
}
|
||||
});
|
||||
}}
|
||||
onSetHistoryTop={(e) => {
|
||||
updateHistory({ ...e, appId });
|
||||
}}
|
||||
onSetCustomTitle={async (e) => {
|
||||
updateHistory({
|
||||
appId,
|
||||
chatId: e.chatId,
|
||||
title: e.title,
|
||||
customTitle: e.title
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* chat container */}
|
||||
<Flex
|
||||
position={'relative'}
|
||||
h={[0, '100%']}
|
||||
w={['100%', 0]}
|
||||
flex={'1 0 0'}
|
||||
flexDirection={'column'}
|
||||
>
|
||||
{/* header */}
|
||||
<ChatHeader
|
||||
appAvatar={chatData.app.avatar}
|
||||
appName={chatData.app.name}
|
||||
history={chatData.history}
|
||||
showHistory={true}
|
||||
onOpenSlider={onOpenSlider}
|
||||
/>
|
||||
{/* chat box */}
|
||||
<Box flex={1}>
|
||||
<ChatBox
|
||||
active={!!chatData.app.name}
|
||||
ref={ChatBoxRef}
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={chatData.userAvatar}
|
||||
userGuideModule={chatData.app?.userGuideModule}
|
||||
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
|
||||
feedbackType={'user'}
|
||||
onUpdateVariable={(e) => {}}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={(e) =>
|
||||
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, outLinkUid })
|
||||
}
|
||||
appId={chatData.appId}
|
||||
chatId={chatId}
|
||||
outLinkUid={outLinkUid}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PageContainer>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const teamId = context?.query?.teamId || '';
|
||||
const appId = context?.query?.appId || '';
|
||||
const chatId = context?.query?.chatId || '';
|
||||
const authToken: string = context?.query?.authToken || '';
|
||||
|
||||
return {
|
||||
props: {
|
||||
teamId,
|
||||
appId,
|
||||
chatId,
|
||||
authToken,
|
||||
...(await serviceSideProps(context))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default OutLink;
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useContext, useCallback, createContext, useState, useMemo, useEffect } from 'react';
|
||||
|
||||
import { formatModelPrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
||||
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -33,7 +34,7 @@ type useImportStoreType = {
|
||||
totalChunkChars: number;
|
||||
totalChunks: number;
|
||||
chunkSize: number;
|
||||
predictPoints: number;
|
||||
predictPrice: number;
|
||||
priceTip: string;
|
||||
uploadRate: number;
|
||||
splitSources2Chunks: () => void;
|
||||
@@ -53,7 +54,7 @@ const StateContext = createContext<useImportStoreType>({
|
||||
totalChunkChars: 0,
|
||||
totalChunks: 0,
|
||||
chunkSize: 0,
|
||||
predictPoints: 0,
|
||||
predictPrice: 0,
|
||||
priceTip: '',
|
||||
uploadRate: 50,
|
||||
splitSources2Chunks: () => {}
|
||||
@@ -104,9 +105,10 @@ const Provider = ({
|
||||
chunkSize: embeddingChunkSize,
|
||||
showChunkInput: true,
|
||||
showPromptInput: false,
|
||||
charsPointsPrice: vectorModel.charsPointsPrice,
|
||||
inputPrice: vectorModel.inputPrice,
|
||||
outputPrice: 0,
|
||||
priceTip: t('core.dataset.import.Embedding Estimated Price Tips', {
|
||||
price: vectorModel.charsPointsPrice
|
||||
price: vectorModel.inputPrice
|
||||
}),
|
||||
uploadRate: 150
|
||||
},
|
||||
@@ -118,9 +120,10 @@ const Provider = ({
|
||||
chunkSize: agentModel.maxContext * 0.55 || 6000,
|
||||
showChunkInput: false,
|
||||
showPromptInput: true,
|
||||
charsPointsPrice: agentModel.charsPointsPrice,
|
||||
inputPrice: agentModel.inputPrice,
|
||||
outputPrice: agentModel.outputPrice,
|
||||
priceTip: t('core.dataset.import.QA Estimated Price Tips', {
|
||||
price: agentModel?.charsPointsPrice
|
||||
price: agentModel?.inputPrice
|
||||
}),
|
||||
uploadRate: 30
|
||||
}
|
||||
@@ -148,12 +151,15 @@ const Provider = ({
|
||||
() => sources.reduce((sum, file) => sum + file.chunkChars, 0),
|
||||
[sources]
|
||||
);
|
||||
const predictPoints = useMemo(() => {
|
||||
const predictPrice = useMemo(() => {
|
||||
if (mode === TrainingModeEnum.qa) {
|
||||
return +(((totalChunkChars * 1.5) / 1000) * agentModel.charsPointsPrice).toFixed(2);
|
||||
const inputTotal = totalChunkChars * selectModelStaticParam.inputPrice;
|
||||
const outputTotal = totalChunkChars * 0.5 * selectModelStaticParam.inputPrice;
|
||||
|
||||
return formatModelPrice2Read(inputTotal + outputTotal);
|
||||
}
|
||||
return +((totalChunkChars / 1000) * vectorModel.charsPointsPrice).toFixed(2);
|
||||
}, [agentModel.charsPointsPrice, mode, totalChunkChars, vectorModel.charsPointsPrice]);
|
||||
return formatModelPrice2Read(totalChunkChars * selectModelStaticParam.inputPrice);
|
||||
}, [mode, selectModelStaticParam.inputPrice, totalChunkChars]);
|
||||
const totalChunks = useMemo(
|
||||
() => sources.reduce((sum, file) => sum + file.chunks.length, 0),
|
||||
[sources]
|
||||
@@ -172,8 +178,7 @@ const Provider = ({
|
||||
return {
|
||||
...file,
|
||||
chunkChars: chars,
|
||||
chunks: chunks.map((chunk, i) => ({
|
||||
chunkIndex: i,
|
||||
chunks: chunks.map((chunk) => ({
|
||||
q: chunk,
|
||||
a: ''
|
||||
}))
|
||||
@@ -193,7 +198,7 @@ const Provider = ({
|
||||
totalChunkChars,
|
||||
totalChunks,
|
||||
chunkSize,
|
||||
predictPoints,
|
||||
predictPrice,
|
||||
splitSources2Chunks
|
||||
};
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
|
||||
|
||||
@@ -46,7 +46,7 @@ function DataProcess({
|
||||
maxChunkSize,
|
||||
totalChunkChars,
|
||||
totalChunks,
|
||||
predictPoints,
|
||||
predictPrice,
|
||||
showRePreview,
|
||||
splitSources2Chunks,
|
||||
priceTip
|
||||
@@ -275,7 +275,7 @@ function DataProcess({
|
||||
{feConfigs?.show_pay && (
|
||||
<MyTooltip label={priceTip}>
|
||||
<Tag colorSchema={'gray'} py={'6px'} borderRadius={'md'} px={3}>
|
||||
{t('core.dataset.import.Estimated points', { points: predictPoints })}
|
||||
{t('core.dataset.import.Estimated Price', { amount: predictPrice, unit: '元' })}
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useImportStore, type FormType } from '../Provider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { postCreateTrainingUsage } from '@/web/support/wallet/usage/api';
|
||||
import { postCreateTrainingBill } from '@/web/support/wallet/bill/api';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { chunksUpload, fileCollectionCreate } from '@/web/core/dataset/utils';
|
||||
import { ImportSourceItemType } from '@/web/core/dataset/type';
|
||||
@@ -54,7 +54,7 @@ const Upload = ({ showPreviewChunks }: { showPreviewChunks: boolean }) => {
|
||||
|
||||
// Batch create collection and upload chunks
|
||||
for await (const item of uploadList) {
|
||||
const billId = await postCreateTrainingUsage({
|
||||
const billId = await postCreateTrainingBill({
|
||||
name: item.sourceName,
|
||||
datasetId: datasetDetail._id
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { getDefaultIndex } from '@fastgpt/global/core/dataset/utils';
|
||||
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { DatasetDataIndexItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import SideTabs from '@/components/SideTabs';
|
||||
import DeleteIcon from '@fastgpt/web/components/common/Icon/delete';
|
||||
@@ -161,10 +162,9 @@ const InputDataModal = ({
|
||||
q: e.q,
|
||||
a: e.a,
|
||||
// remove dataId
|
||||
indexes: e.indexes.map((index) => ({
|
||||
...index,
|
||||
dataId: undefined
|
||||
}))
|
||||
indexes: e.indexes.map((index) =>
|
||||
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a }) : index
|
||||
)
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -195,7 +195,7 @@ const InputDataModal = ({
|
||||
id: dataId,
|
||||
...e,
|
||||
indexes: e.indexes.map((index) =>
|
||||
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a, dataId: index.dataId }) : index
|
||||
index.defaultIndex ? getDefaultIndex({ q: e.q, a: e.a }) : index
|
||||
)
|
||||
});
|
||||
|
||||
@@ -278,7 +278,7 @@ const InputDataModal = ({
|
||||
bg={i % 2 !== 0 ? 'myWhite.400' : ''}
|
||||
_hover={{
|
||||
'& .delete': {
|
||||
display: index.defaultIndex ? 'none' : 'block'
|
||||
display: index.defaultIndex && indexes.length === 1 ? 'none' : 'block'
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -331,6 +331,7 @@ const InputDataModal = ({
|
||||
onClick={() =>
|
||||
appendIndexes({
|
||||
defaultIndex: false,
|
||||
type: DatasetDataIndexTypeEnum.chunk,
|
||||
text: '',
|
||||
dataId: `${Date.now()}`
|
||||
})
|
||||
@@ -382,47 +383,45 @@ const InputTab = ({
|
||||
const [inputType, setInputType] = useState(InputTypeEnum.q);
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} h={'100%'}>
|
||||
<Box>
|
||||
<RowTabs
|
||||
list={[
|
||||
{
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box as="span" color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
{t('core.dataset.data.Main Content')}
|
||||
<MyTooltip label={t('core.dataset.data.Data Content Tip')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
),
|
||||
value: InputTypeEnum.q
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
{t('core.dataset.data.Auxiliary Data')}
|
||||
<MyTooltip label={t('core.dataset.data.Auxiliary Data Tip')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
),
|
||||
value: InputTypeEnum.a
|
||||
}
|
||||
]}
|
||||
value={inputType}
|
||||
onChange={(e) => setInputType(e as InputTypeEnum)}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<RowTabs
|
||||
list={[
|
||||
{
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box as="span" color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
{t('core.dataset.data.Main Content')}
|
||||
<MyTooltip label={t('core.dataset.data.Data Content Tip')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
),
|
||||
value: InputTypeEnum.q
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
{t('core.dataset.data.Auxiliary Data')}
|
||||
<MyTooltip label={t('core.dataset.data.Auxiliary Data Tip')}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
),
|
||||
value: InputTypeEnum.a
|
||||
}
|
||||
]}
|
||||
value={inputType}
|
||||
onChange={(e) => setInputType(e as InputTypeEnum)}
|
||||
/>
|
||||
|
||||
<Box mt={3} flex={'1 0 0'}>
|
||||
<Box mt={3}>
|
||||
{inputType === InputTypeEnum.q && (
|
||||
<Textarea
|
||||
placeholder={t('core.dataset.data.Data Content Placeholder', { maxToken })}
|
||||
maxLength={maxToken}
|
||||
h={'100%'}
|
||||
rows={isPc ? 24 : 12}
|
||||
bg={'myWhite.400'}
|
||||
{...register(`q`, {
|
||||
required: true
|
||||
@@ -434,7 +433,6 @@ const InputTab = ({
|
||||
placeholder={t('core.dataset.data.Auxiliary Data Placeholder', {
|
||||
maxToken: maxToken * 1.5
|
||||
})}
|
||||
h={'100%'}
|
||||
bg={'myWhite.400'}
|
||||
rows={isPc ? 24 : 12}
|
||||
maxLength={maxToken * 1.5}
|
||||
@@ -442,6 +440,6 @@ const InputTab = ({
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag';
|
||||
import Head from 'next/head';
|
||||
|
||||
const DataCard = dynamic(() => import('./components/DataCard'));
|
||||
const Test = dynamic(() => import('./components/Test'));
|
||||
@@ -146,9 +145,7 @@ const Detail = ({ datasetId, currentTab }: { datasetId: string; currentTab: `${T
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{datasetDetail?.name}</title>
|
||||
</Head>
|
||||
<Script src="/js/pdf.js" strategy="lazyOnload"></Script>
|
||||
<PageContainer>
|
||||
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
|
||||
{isPc ? (
|
||||
|
||||
@@ -7,111 +7,92 @@ import {
|
||||
NumberIncrementStepper,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
Button
|
||||
Button,
|
||||
useDisclosure,
|
||||
ModalBody,
|
||||
ModalFooter
|
||||
} from '@chakra-ui/react';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import MySelect from '@/components/Select';
|
||||
import {
|
||||
SubStatusEnum,
|
||||
SubTypeEnum,
|
||||
subSelectMap
|
||||
} from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import {
|
||||
posCheckTeamDatasetSizeSub,
|
||||
postUpdateTeamDatasetSizeSub,
|
||||
putTeamDatasetSubStatus
|
||||
} from '@/web/support/wallet/sub/api';
|
||||
import { SubDatasetSizePreviewCheckResponse } from '@fastgpt/global/support/wallet/sub/api.d';
|
||||
import { useRouter } from 'next/router';
|
||||
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 { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import MyModal from '@/components/MyModal';
|
||||
|
||||
const ExtraPlan = () => {
|
||||
const ExtraPlan = ({ extraDatasetSize }: { extraDatasetSize?: TeamSubSchema }) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const { subPlans } = useSystemStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
|
||||
// extra dataset
|
||||
const extraDatasetPrice = subPlans?.extraDatasetSize?.price || 0;
|
||||
const { register: registerDatasetSize, handleSubmit: handleSubmitDatasetSize } = useForm({
|
||||
defaultValues: {
|
||||
datasetSize: 0,
|
||||
month: 1
|
||||
}
|
||||
});
|
||||
const onclickBuyDatasetSize = useCallback(
|
||||
async ({ datasetSize, month }: { datasetSize: number; month: number }) => {
|
||||
try {
|
||||
const datasetSizePayAmount = datasetSize * month * extraDatasetPrice;
|
||||
if (datasetSizePayAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '购买数量不能为0'
|
||||
});
|
||||
}
|
||||
setLoading(true);
|
||||
const [datasetSize, setDatasetSize] = useState(0);
|
||||
const [isRenew, setIsRenew] = useState('false');
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
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'
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
const [confirmPayExtraDatasetSizeData, setConfirmPayExtraDatasetSizeData] =
|
||||
useState<SubDatasetSizePreviewCheckResponse>();
|
||||
|
||||
useEffect(() => {
|
||||
setDatasetSize((extraDatasetSize?.nextExtraDatasetSize || 0) / 1000);
|
||||
setIsRenew(extraDatasetSize?.status === SubStatusEnum.active ? 'true' : 'false');
|
||||
}, [extraDatasetSize]);
|
||||
|
||||
const { mutate: onUpdateExtraDatasetSizeStatus } = useRequest({
|
||||
mutationFn: (e: 'true' | 'false') => {
|
||||
setIsRenew(e);
|
||||
return putTeamDatasetSubStatus({
|
||||
status: subSelectMap[e],
|
||||
type: SubTypeEnum.extraDatasetSize
|
||||
});
|
||||
},
|
||||
[extraDatasetPrice, toast]
|
||||
);
|
||||
|
||||
// extra ai points
|
||||
const extraPointsPrice = subPlans?.extraPoints?.price || 0;
|
||||
const { register: registerExtraPoints, handleSubmit: handleSubmitExtraPoints } = useForm({
|
||||
defaultValues: {
|
||||
points: 0,
|
||||
month: 1
|
||||
}
|
||||
successToast: t('common.Update success'),
|
||||
errorToast: t('common.error.Update error')
|
||||
});
|
||||
const onclickBuyExtraPoints = useCallback(
|
||||
async ({ points, month }: { points: number; month: number }) => {
|
||||
try {
|
||||
const payAmount = points * month * extraPointsPrice;
|
||||
|
||||
if (payAmount === 0) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '购买数量不能为0'
|
||||
});
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
const res = await getWxPayQRCode({
|
||||
type: BillTypeEnum.extraPoints,
|
||||
month,
|
||||
extraPoints: points
|
||||
});
|
||||
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: getErrText(err),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
},
|
||||
[extraPointsPrice, toast]
|
||||
const { mutate: onClickUpdateExtraDatasetPlan, isLoading: isPayingExtraDatasetSize } = useRequest(
|
||||
{
|
||||
mutationFn: () => postUpdateTeamDatasetSizeSub({ size: datasetSize }),
|
||||
onSuccess() {
|
||||
setTimeout(() => {
|
||||
router.reload();
|
||||
}, 100);
|
||||
},
|
||||
successToast: t('common.Update success'),
|
||||
errorToast: t('common.error.Update error')
|
||||
}
|
||||
);
|
||||
const { mutate: onClickPreviewCheck, isLoading: isFetchingPreviewCheck } = useRequest({
|
||||
mutationFn: () =>
|
||||
posCheckTeamDatasetSizeSub({
|
||||
size: datasetSize
|
||||
}),
|
||||
onSuccess(res: SubDatasetSizePreviewCheckResponse) {
|
||||
if (!res.payForNewSub) {
|
||||
onClickUpdateExtraDatasetPlan('');
|
||||
return;
|
||||
} else {
|
||||
setConfirmPayExtraDatasetSizeData(res);
|
||||
}
|
||||
},
|
||||
errorToast: t('common.error.Update error')
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -120,13 +101,13 @@ const ExtraPlan = () => {
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box id={'extra-plan'} fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
{t('support.wallet.subscription.Extra plan')}
|
||||
</Box>
|
||||
<Box mt={8} mb={10} color={'myGray.500'} fontSize={'lg'}>
|
||||
{t('support.wallet.subscription.Extra plan tip')}
|
||||
</Box>
|
||||
<Grid mt={8} gridTemplateColumns={['1fr', '1fr 1fr']} gap={5} w={['100%', 'auto']}>
|
||||
<Grid mt={8} gridTemplateColumns={['1fr', '1fr']}>
|
||||
<Box
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
px={'32px'}
|
||||
@@ -135,60 +116,78 @@ const ExtraPlan = () => {
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
boxShadow={'1.5'}
|
||||
w={['100%', '500px']}
|
||||
>
|
||||
<Flex borderBottomWidth={'1px'} borderBottomColor={'myGray.200'}>
|
||||
<Flex w={['100%', '500px']} borderBottomWidth={'1px'} borderBottomColor={'myGray.200'}>
|
||||
<Box flex={'1 0 0'}>
|
||||
<Box fontSize={'xl'} color={'primary.600'}>
|
||||
{t('support.wallet.subscription.Extra dataset size')}
|
||||
</Box>
|
||||
<Box mt={3} fontSize={['28px', '32px']} fontWeight={'bold'}>
|
||||
¥{extraDatasetPrice}/1000组{' '}
|
||||
<Box mt={3} fontSize={['32px', '38px']} fontWeight={'bold'}>
|
||||
¥{extraDatasetPrice}/1k组{' '}
|
||||
<Box ml={1} as={'span'} fontSize={'lg'} color={'myGray.600'} fontWeight={'normal'}>
|
||||
/{t('common.month')}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<MyIcon
|
||||
display={['none', 'display']}
|
||||
mt={'-30px'}
|
||||
transform={'translateX(20px)'}
|
||||
name={'support/bill/extraDatasetsize'}
|
||||
transform={'translate(20px,-20px)'}
|
||||
name={'support/pay/extraDatasetsize'}
|
||||
fill={'none'}
|
||||
/>
|
||||
</Flex>
|
||||
<Box>
|
||||
<Flex mt={4}>
|
||||
<MyIcon mr={2} name={'support/bill/shoppingCart'} w={'16px'} color={'primary.600'} />
|
||||
购买资源包
|
||||
</Flex>
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box flex={['0 0 100px', '0 0 200px']}>
|
||||
{t('support.wallet.subscription.Month amount')}
|
||||
<Box flex={'0 0 200px'}>
|
||||
{t('support.wallet.subscription.Current dataset store')}:{' '}
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
|
||||
<NumberInput size={'sm'} flex={1} step={1} min={1} max={12} position={'relative'}>
|
||||
<NumberInputField
|
||||
pr={'30px'}
|
||||
{...registerDatasetSize('month', {
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 12,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
<Box position={'absolute'} right={'20px'} color={'myGray.500'} fontSize={'xs'}>
|
||||
{t('common.month')}
|
||||
<Box fontWeight={'bold'} flex={1}>
|
||||
{extraDatasetSize?.currentExtraDatasetSize || 0}
|
||||
{t('core.dataset.data.unit')}
|
||||
</Box>
|
||||
</Flex>
|
||||
{extraDatasetSize?.nextExtraDatasetSize !== undefined && (
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 200px'}>
|
||||
{t('support.wallet.subscription.Next sub dataset size')}:
|
||||
</Box>
|
||||
<Box fontWeight={'bold'} flex={1}>
|
||||
{extraDatasetSize?.nextExtraDatasetSize || 0}
|
||||
{t('core.dataset.data.unit')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!extraDatasetSize?.startTime && (
|
||||
<Flex mt={3}>
|
||||
<Box flex={'0 0 200px'}>订阅开始时间: </Box>
|
||||
<Box>{formatTime2YMDHM(extraDatasetSize?.startTime)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!extraDatasetSize?.expiredTime && (
|
||||
<Flex mt={3}>
|
||||
<Box flex={'0 0 200px'}>订阅到期时间: </Box>
|
||||
<Box>{formatTime2YMDHM(extraDatasetSize?.expiredTime)}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<Box flex={'0 0 200px'}>是否自动续费: </Box>
|
||||
<MySelect
|
||||
value={isRenew}
|
||||
size={'sm'}
|
||||
w={'180px'}
|
||||
bg={'myGray.50'}
|
||||
boxShadow={'none'}
|
||||
list={[
|
||||
{ label: '自动续费', value: 'true' },
|
||||
{ label: '不自动续费', value: 'false' }
|
||||
]}
|
||||
onchange={(e) => {
|
||||
if (!extraDatasetSize) return;
|
||||
onUpdateExtraDatasetSizeStatus(e);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box flex={['0 0 100px', '0 0 200px']}>
|
||||
<Box flex={'0 0 200px'}>
|
||||
{t('support.wallet.subscription.Update extra dataset size')}
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
|
||||
@@ -198,18 +197,13 @@ const ExtraPlan = () => {
|
||||
min={0}
|
||||
max={10000}
|
||||
step={1}
|
||||
value={datasetSize}
|
||||
position={'relative'}
|
||||
onChange={(e) => {
|
||||
setDatasetSize(Number(e));
|
||||
}}
|
||||
>
|
||||
<NumberInputField
|
||||
pr={'30px'}
|
||||
{...registerDatasetSize('datasetSize', {
|
||||
required: true,
|
||||
min: 0,
|
||||
max: 10000,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
step={1}
|
||||
/>
|
||||
<NumberInputField pr={'30px'} value={datasetSize} step={1} min={0} max={10000} />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
@@ -221,124 +215,88 @@ const ExtraPlan = () => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
isDisabled={datasetSize * 1000 === extraDatasetSize?.nextExtraDatasetSize}
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={loading}
|
||||
onClick={handleSubmitDatasetSize(onclickBuyDatasetSize)}
|
||||
isLoading={isPayingExtraDatasetSize || isFetchingPreviewCheck}
|
||||
onClick={onClickPreviewCheck}
|
||||
>
|
||||
{t('support.wallet.Buy')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
{/* points */}
|
||||
<Box
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
w={['100%', '500px']}
|
||||
px={'32px'}
|
||||
py={'24px'}
|
||||
borderRadius={'2xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
boxShadow={'1.5'}
|
||||
>
|
||||
<Flex borderBottomWidth={'1px'} borderBottomColor={'myGray.200'}>
|
||||
<Box flex={'1 0 0'}>
|
||||
<Box fontSize={'xl'} color={'primary.600'}>
|
||||
{t('support.wallet.subscription.Extra ai points')}
|
||||
</Box>
|
||||
<Box mt={3} fontSize={['28px', '32px']} fontWeight={'bold'}>
|
||||
¥{extraPointsPrice}/1000积分{' '}
|
||||
<Box ml={1} as={'span'} fontSize={'lg'} color={'myGray.600'} fontWeight={'normal'}>
|
||||
/{t('common.month')}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<MyIcon
|
||||
display={['none', 'display']}
|
||||
mt={'-30px'}
|
||||
transform={'translateX(20px)'}
|
||||
name={'support/bill/extraPoints'}
|
||||
fill={'none'}
|
||||
/>
|
||||
</Flex>
|
||||
<Box>
|
||||
<Flex mt={4}>
|
||||
<MyIcon mr={2} name={'support/bill/shoppingCart'} w={'16px'} color={'primary.600'} />
|
||||
购买资源包
|
||||
</Flex>
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box flex={['0 0 100px', '0 0 200px']}>
|
||||
{t('support.wallet.subscription.Month amount')}
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
|
||||
<NumberInput size={'sm'} flex={1} step={1} min={1} max={12} position={'relative'}>
|
||||
<NumberInputField
|
||||
pr={'30px'}
|
||||
{...registerExtraPoints('month', {
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 12,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
<Box position={'absolute'} right={'20px'} color={'myGray.500'} fontSize={'xs'}>
|
||||
{t('common.month')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex mt={4} alignItems={'center'}>
|
||||
<Box flex={['0 0 100px', '0 0 200px']}>
|
||||
{t('support.wallet.subscription.Update extra ai points')}
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={1} w={'180px'} position={'relative'}>
|
||||
<NumberInput
|
||||
size={'sm'}
|
||||
flex={1}
|
||||
min={0}
|
||||
max={10000}
|
||||
step={1}
|
||||
position={'relative'}
|
||||
>
|
||||
<NumberInputField
|
||||
pr={'30px'}
|
||||
step={1}
|
||||
{...registerExtraPoints('points', {
|
||||
required: true,
|
||||
min: 0,
|
||||
max: 10000,
|
||||
valueAsNumber: true
|
||||
})}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
<Box position={'absolute'} right={'20px'} color={'myGray.500'} fontSize={'xs'}>
|
||||
000积分
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
mt={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={loading}
|
||||
onClick={handleSubmitExtraPoints(onclickBuyExtraPoints)}
|
||||
>
|
||||
{t('support.wallet.Buy')}
|
||||
{t('common.change')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{!!qrPayData && <QRCodePayModal {...qrPayData} />}
|
||||
{/* extra dataset size modal */}
|
||||
{!!confirmPayExtraDatasetSizeData && (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={() => setConfirmPayExtraDatasetSizeData(undefined)}
|
||||
title={t('support.wallet.Confirm pay')}
|
||||
iconSrc="common/confirm/rightTip"
|
||||
>
|
||||
<ModalBody px={8} py={5}>
|
||||
<Flex>
|
||||
<Box flex={'0 0 120px'} color={'myGray.600'}>
|
||||
当前额外容量
|
||||
</Box>
|
||||
<Box>{extraDatasetSize?.currentExtraDatasetSize || 0}条</Box>
|
||||
</Flex>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 120px'} color={'myGray.600'}>
|
||||
新的额外容量
|
||||
</Box>
|
||||
<Box>{confirmPayExtraDatasetSizeData.newSubSize}条</Box>
|
||||
</Flex>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 120px'} color={'myGray.600'}>
|
||||
新套餐价格
|
||||
</Box>
|
||||
<Box>{formatStorePrice2Read(confirmPayExtraDatasetSizeData.newPlanPrice)}元</Box>
|
||||
</Flex>
|
||||
<Flex mt={4}>
|
||||
<Box flex={'0 0 120px'} color={'myGray.600'}>
|
||||
有效时长
|
||||
</Box>
|
||||
<Box>30天</Box>
|
||||
</Flex>
|
||||
|
||||
{/* <Flex>
|
||||
<Box flex={'0 0 120px'}>账号余额:</Box>
|
||||
<Box>{formatStorePrice2Read(userInfo?.team?.balance).toFixed(3)}元</Box>
|
||||
</Flex> */}
|
||||
</ModalBody>
|
||||
<ModalFooter mx={8} px={0} borderTopWidth={'1px'} borderTopColor={'myGray.200'}>
|
||||
<Box color={'myGray.600'}>账号余额:</Box>
|
||||
{confirmPayExtraDatasetSizeData.balanceEnough ? (
|
||||
<>
|
||||
<Box flex={'1 0 0'}>
|
||||
{formatStorePrice2Read(userInfo?.team?.balance).toFixed(2)}元
|
||||
</Box>
|
||||
<Button
|
||||
isLoading={isPayingExtraDatasetSize}
|
||||
onClick={() => onClickUpdateExtraDatasetPlan('')}
|
||||
>
|
||||
支付{formatStorePrice2Read(confirmPayExtraDatasetSizeData.payPrice).toFixed(2)}元
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Box color={'red.600'} flex={'1 0 0'}>
|
||||
余额不足
|
||||
</Box>
|
||||
<Button
|
||||
isLoading={isPayingExtraDatasetSize}
|
||||
onClick={() => router.push('/account')}
|
||||
>
|
||||
去充值
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,40 +4,7 @@ import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FAQ = () => {
|
||||
const { t } = useTranslation();
|
||||
const faqs = [
|
||||
{
|
||||
title: '订阅套餐会自动续费么?',
|
||||
desc: '当前套餐过期后,系统会自动根据“未来套餐”进行续费,系统会尝试从账户余额进行扣费,如果您需要自动续费,请在账户余额中预留额度。'
|
||||
},
|
||||
{
|
||||
title: '能否切换订阅套餐?',
|
||||
desc: '当前套餐价格大于新套餐时,无法立即切换,将会在当前套餐过期后以“续费”形式进行切换。\n当前套餐价格小于新套餐时,系统会自动计算当前套餐剩余余额,您可支付差价进行套餐切换。'
|
||||
},
|
||||
{
|
||||
title: '什么是AI积分?',
|
||||
desc: '每次调用AI模型时,都会消耗一定的AI积分。具体的计算标准可参考上方的“AI 积分计算标准”。'
|
||||
},
|
||||
{
|
||||
title: 'AI积分会过期么?',
|
||||
desc: '会过期。当前套餐过期后,AI积分将会清空,并更新为新套餐的AI积分。年度套餐的AI积分时长为1年,而不是每个月。'
|
||||
},
|
||||
{
|
||||
title: '知识库索引怎么计算?',
|
||||
desc: '知识库索引是系统存储的最小单位。通常每条知识库数据对应一条索引,但也会有多条索引的情况。你可以在知识库数据的编辑面板,查看该数据的索引数量和具体内容。'
|
||||
},
|
||||
{
|
||||
title: '额外资源包可以叠加么?',
|
||||
desc: '可以的。每次购买的资源包都是独立的,在其有效期内将会叠加使用。AI积分会优先扣除最先过期的资源包。'
|
||||
},
|
||||
{
|
||||
title: '知识库索引超出会删除么?',
|
||||
desc: '不会,知识库索引超出时,仅无法插入新的知识库索引。'
|
||||
},
|
||||
{
|
||||
title: '免费版数据会清除么?',
|
||||
desc: '免费版用户15天无使用记录后,会自动清除所有知识库内容。'
|
||||
}
|
||||
];
|
||||
const faqs = [{ title: '怎么付费', describe: '2222' }];
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -51,25 +18,22 @@ const FAQ = () => {
|
||||
{t('support.wallet.subscription.FAQ')}
|
||||
</Box>
|
||||
<Grid mt={4} gridTemplateColumns={['1fr', '1fr 1fr']} gap={4} w={'100%'}>
|
||||
{faqs.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
py={4}
|
||||
px={5}
|
||||
borderRadius={'lg'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
bg={'rgba(255,255,255,0.9)'}
|
||||
_hover={{
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
>
|
||||
<Box fontWeight={'bold'}>{item.title}</Box>
|
||||
<Box fontSize={'sm'} color={'myGray.600'} whiteSpace={'pre-wrap'}>
|
||||
{item.desc}
|
||||
</Box>
|
||||
<Box
|
||||
py={2}
|
||||
px={4}
|
||||
borderRadius={'lg'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
bg={'rgba(255,255,255,0.9)'}
|
||||
_hover={{
|
||||
borderColor: 'primary.300'
|
||||
}}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'500'}>
|
||||
怎么付费
|
||||
</Box>
|
||||
))}
|
||||
<Box color={'myGray.500'}>2222</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ const Points = () => {
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box id="point-card" fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
<Box fontWeight={'bold'} fontSize={['24px', '36px']}>
|
||||
{t('support.wallet.subscription.Ai points')}
|
||||
</Box>
|
||||
<Grid gap={6} mt={['30px', '48px']} w={'100%'}>
|
||||
@@ -42,7 +42,7 @@ const Points = () => {
|
||||
{llmModelList?.map((item, i) => (
|
||||
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
|
||||
<Box flex={'1 0 0'}>{item.name}</Box>
|
||||
<Box flex={'1 0 0'}>{item.charsPointsPrice}积分 / 1000字符</Box>
|
||||
<Box flex={'1 0 0'}>5积分 / 1000字符</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
@@ -67,7 +67,7 @@ const Points = () => {
|
||||
{vectorModelList?.map((item, i) => (
|
||||
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
|
||||
<Box flex={'1 0 0'}>{item.name}</Box>
|
||||
<Box flex={'1 0 0'}>{item.charsPointsPrice}积分 / 1000字符</Box>
|
||||
<Box flex={'1 0 0'}>5积分 / 1000字符</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
@@ -89,7 +89,7 @@ const Points = () => {
|
||||
{audioSpeechModelList?.map((item, i) => (
|
||||
<Flex key={item.model} py={4} bg={i % 2 !== 0 ? 'myGray.50' : ''}>
|
||||
<Box flex={'1 0 0'}>{item.name}</Box>
|
||||
<Box flex={'1 0 0'}>{item.charsPointsPrice}积分 / 1000字符</Box>
|
||||
<Box flex={'1 0 0'}>5积分 / 1000字符</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
@@ -110,7 +110,7 @@ const Points = () => {
|
||||
<Box flex={4} textAlign={'center'} h={'100%'}>
|
||||
<Flex py={4}>
|
||||
<Box flex={'1 0 0'}>{whisperModel?.name}</Box>
|
||||
<Box flex={'1 0 0'}>{whisperModel?.charsPointsPrice}积分 / 分钟</Box>
|
||||
<Box flex={'1 0 0'}>{whisperModel?.inputPrice}积分 / 分钟</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, Button, Flex, Grid, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Grid } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { postCheckStandardSub, postUpdateStandardSub } from '@/web/support/wallet/sub/api';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
@@ -10,21 +11,9 @@ import { StandardSubPlanParams } from '@fastgpt/global/support/wallet/sub/api';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import { StandardSubPlanUpdateResponse } from '@fastgpt/global/support/wallet/sub/api.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/usage/tools';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
import { formatStorePrice2Read } from '@fastgpt/global/support/wallet/bill/tools';
|
||||
import { TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import QRCodePayModal, { type QRPayProps } from '@/components/support/wallet/QRCodePayModal';
|
||||
import { getWxPayQRCode } from '@/web/support/wallet/bill/api';
|
||||
import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants';
|
||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||
|
||||
type ConfirmPayModalProps = {
|
||||
teamBalance: number;
|
||||
totalPrice: number;
|
||||
payPrice: number;
|
||||
|
||||
planProps: StandardSubPlanParams;
|
||||
};
|
||||
|
||||
const Standard = ({
|
||||
standardPlan,
|
||||
@@ -36,7 +25,7 @@ const Standard = ({
|
||||
const { t } = useTranslation();
|
||||
const { subPlans, feConfigs } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
const [confirmPayData, setConfirmPayData] = useState<ConfirmPayModalProps>();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
|
||||
const [selectSubMode, setSelectSubMode] = useState<`${SubModeEnum}`>(SubModeEnum.month);
|
||||
|
||||
@@ -52,12 +41,12 @@ const Standard = ({
|
||||
maxDatasetAmount: value.maxDatasetAmount,
|
||||
chatHistoryStoreDuration: value.chatHistoryStoreDuration,
|
||||
maxDatasetSize: value.maxDatasetSize,
|
||||
permissionCustomApiKey: value.permissionCustomApiKey,
|
||||
permissionCustomCopyright: value.permissionCustomCopyright,
|
||||
customApiKey: value.customApiKey,
|
||||
customCopyright: value.customCopyright,
|
||||
trainingWeight: value.trainingWeight,
|
||||
permissionReRank: value.permissionReRank,
|
||||
reRankWeight: value.reRankWeight,
|
||||
totalPoints: value.totalPoints * (selectSubMode === SubModeEnum.month ? 1 : 12),
|
||||
permissionWebsiteSync: value.permissionWebsiteSync
|
||||
websiteSyncInterval: value.websiteSyncInterval
|
||||
};
|
||||
})
|
||||
: [];
|
||||
@@ -75,21 +64,41 @@ const Standard = ({
|
||||
const { mutate: onclickPreCheckStandPlan, isLoading: isCheckingStandardPlan } = useRequest({
|
||||
mutationFn: (data: StandardSubPlanParams) => postCheckStandardSub(data),
|
||||
onSuccess(res: StandardSubPlanUpdateResponse) {
|
||||
if (!res.balanceEnough) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('support.wallet.Balance not enough tip')
|
||||
});
|
||||
}
|
||||
if (res.payPrice === undefined) {
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
});
|
||||
} else if (res.payPrice > 0) {
|
||||
openConfirm(
|
||||
() =>
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
}),
|
||||
undefined,
|
||||
t('support.wallet.subscription.Standard plan pay confirm', {
|
||||
payPrice: formatStorePrice2Read(res.payPrice).toFixed(2)
|
||||
})
|
||||
)();
|
||||
} else {
|
||||
setConfirmPayData({
|
||||
teamBalance: res.teamBalance,
|
||||
totalPrice: res.planPrice,
|
||||
payPrice: res.payPrice,
|
||||
planProps: {
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
}
|
||||
});
|
||||
openConfirm(
|
||||
() =>
|
||||
onclickUpdateStandardPlan({
|
||||
level: res.nextSubLevel,
|
||||
mode: res.nextMode
|
||||
}),
|
||||
undefined,
|
||||
t('support.wallet.subscription.Refund plan and pay confirm', {
|
||||
amount: formatStorePrice2Read(Math.abs(res.payPrice)).toFixed(2)
|
||||
})
|
||||
)();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -129,114 +138,148 @@ const Standard = ({
|
||||
gap={[4, 6, 8]}
|
||||
w={'100%'}
|
||||
>
|
||||
{standardSubList.map((item) => {
|
||||
const isCurrentPlan =
|
||||
item.level === standardPlan?.currentSubLevel &&
|
||||
selectSubMode === standardPlan?.currentMode;
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={item.level}
|
||||
flex={'1 0 0'}
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
p={'28px'}
|
||||
borderRadius={'2xl'}
|
||||
borderWidth={'1.5px'}
|
||||
boxShadow={'1.5'}
|
||||
{...(isCurrentPlan
|
||||
? {
|
||||
borderColor: 'primary.600'
|
||||
}
|
||||
: {
|
||||
borderColor: 'myGray.150'
|
||||
})}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'500'}>
|
||||
{t(item.label)}
|
||||
</Box>
|
||||
<Box fontSize={['32px', '42px']} fontWeight={'bold'}>
|
||||
¥{item.price}
|
||||
</Box>
|
||||
<Box color={'myGray.500'} h={'40px'} fontSize={'xs'}>
|
||||
{t(item.desc, { title: feConfigs?.systemTitle })}
|
||||
</Box>
|
||||
{(() => {
|
||||
if (
|
||||
item.level === StandardSubLevelEnum.free &&
|
||||
selectSubMode === SubModeEnum.year
|
||||
) {
|
||||
return (
|
||||
<Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}>
|
||||
{t('support.wallet.subscription.Nonsupport')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (
|
||||
item.level === standardPlan?.nextSubLevel &&
|
||||
selectSubMode === standardPlan?.nextMode
|
||||
) {
|
||||
return (
|
||||
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
|
||||
{t('support.wallet.subscription.Next plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (isCurrentPlan) {
|
||||
return (
|
||||
<Button
|
||||
mt={4}
|
||||
mb={6}
|
||||
w={'100%'}
|
||||
variant={'whiteBase'}
|
||||
isDisabled={
|
||||
item.level === standardPlan?.nextSubLevel &&
|
||||
selectSubMode === standardPlan?.nextMode
|
||||
}
|
||||
onClick={() =>
|
||||
onclickPreCheckStandPlan({
|
||||
level: item.level,
|
||||
mode: selectSubMode
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('support.wallet.subscription.Current plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
{standardSubList.map((item) => (
|
||||
<Box
|
||||
key={item.level}
|
||||
bg={'rgba(255, 255, 255, 0.90)'}
|
||||
p={'28px'}
|
||||
borderRadius={'2xl'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'myGray.150'}
|
||||
boxShadow={'1.5'}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'500'}>
|
||||
{t(item.label)}
|
||||
</Box>
|
||||
<Box fontSize={['32px', '42px']} fontWeight={'bold'}>
|
||||
¥{item.price}
|
||||
</Box>
|
||||
<Box color={'myGray.500'} h={'40px'}>
|
||||
{t(item.desc, { title: feConfigs?.systemTitle })}
|
||||
</Box>
|
||||
{(() => {
|
||||
if (item.level === StandardSubLevelEnum.free && selectSubMode === SubModeEnum.year) {
|
||||
return (
|
||||
<Button
|
||||
mt={4}
|
||||
mb={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={isUpdatingStandardPlan || isCheckingStandardPlan}
|
||||
onClick={() =>
|
||||
onclickPreCheckStandPlan({
|
||||
level: item.level,
|
||||
mode: selectSubMode
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('support.wallet.subscription.Buy now')}
|
||||
<Button isDisabled mt={4} mb={6} w={'100%'} variant={'solid'}>
|
||||
{t('support.wallet.subscription.Nonsupport')}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
}
|
||||
if (
|
||||
item.level === standardPlan?.currentSubLevel &&
|
||||
selectSubMode === standardPlan?.currentMode
|
||||
) {
|
||||
return (
|
||||
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
|
||||
{t('support.wallet.subscription.Current plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
if (
|
||||
item.level === standardPlan?.nextSubLevel &&
|
||||
selectSubMode === standardPlan?.nextMode
|
||||
) {
|
||||
return (
|
||||
<Button mt={4} mb={6} w={'100%'} variant={'whiteBase'} isDisabled>
|
||||
{t('support.wallet.subscription.Next plan')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
mt={4}
|
||||
mb={6}
|
||||
w={'100%'}
|
||||
variant={'primaryGhost'}
|
||||
isLoading={isUpdatingStandardPlan || isCheckingStandardPlan}
|
||||
onClick={() =>
|
||||
onclickPreCheckStandPlan({
|
||||
level: item.level,
|
||||
mode: selectSubMode
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('support.wallet.subscription.Buy now')}
|
||||
</Button>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* function list */}
|
||||
<StandardPlanContentList level={item.level} mode={selectSubMode} />
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{/* function list */}
|
||||
<Grid gap={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max members', {
|
||||
amount: item.maxTeamMember
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max app', {
|
||||
amount: item.maxAppAmount
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max dataset', {
|
||||
amount: item.maxDatasetAmount
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.History store', {
|
||||
amount: item.chatHistoryStoreDuration
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Max dataset size', {
|
||||
amount: item.maxDatasetSize
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.function.Points', {
|
||||
amount: item.totalPoints
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>
|
||||
{t('support.wallet.subscription.Training weight', {
|
||||
weight: item.trainingWeight
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
{!!item.customApiKey && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>个人API Key</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{!!item.websiteSyncInterval && (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'price/right'} w={'16px'} mr={3} />
|
||||
<Box color={'myGray.600'}>{item.websiteSyncInterval} h/次 web站点同步</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{!!confirmPayData && (
|
||||
<ConfirmPayModal
|
||||
{...confirmPayData}
|
||||
onClose={() => setConfirmPayData(undefined)}
|
||||
onConfirmPay={() => onclickUpdateStandardPlan(confirmPayData.planProps)}
|
||||
/>
|
||||
)}
|
||||
<ConfirmModal />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -295,87 +338,3 @@ const RowTabs = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ConfirmPayModal = ({
|
||||
teamBalance,
|
||||
totalPrice,
|
||||
payPrice,
|
||||
onClose,
|
||||
onConfirmPay
|
||||
}: ConfirmPayModalProps & { onClose: () => void; onConfirmPay: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [qrPayData, setQRPayData] = useState<QRPayProps>();
|
||||
|
||||
const formatPayPrice = Math.ceil(formatStorePrice2Read(payPrice));
|
||||
const formatTeamBalance = Math.floor(formatStorePrice2Read(teamBalance));
|
||||
|
||||
const { mutate: handleClickPay, isLoading } = useRequest({
|
||||
mutationFn: async (amount: number) => {
|
||||
// 获取支付二维码
|
||||
return getWxPayQRCode({
|
||||
type: BillTypeEnum.balance,
|
||||
balance: amount
|
||||
});
|
||||
},
|
||||
onSuccess(res) {
|
||||
setQRPayData({
|
||||
readPrice: res.readPrice,
|
||||
codeUrl: res.codeUrl,
|
||||
billId: res.billId
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="modal/confirmPay"
|
||||
title={t('support.wallet.Confirm pay')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody py={5} px={9}>
|
||||
<Flex>
|
||||
<Box flex={'0 0 100px'}>新套餐价格</Box>
|
||||
<Box>{formatStorePrice2Read(totalPrice)}元</Box>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Box flex={'0 0 100px'}>旧套餐余额</Box>
|
||||
<Box>{Math.floor(formatStorePrice2Read(totalPrice - payPrice))}元</Box>
|
||||
</Flex>
|
||||
<Flex mt={6}>
|
||||
<Box flex={'0 0 100px'}>需支付</Box>
|
||||
<Box>{formatPayPrice}元</Box>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter
|
||||
borderTopWidth={'1px'}
|
||||
borderTopColor={'borderColor.base'}
|
||||
mx={9}
|
||||
justifyContent={'flex-start'}
|
||||
px={0}
|
||||
>
|
||||
<Box>账号余额: </Box>
|
||||
<Box ml={2} flex={1}>
|
||||
{formatTeamBalance}元
|
||||
</Box>
|
||||
{teamBalance >= payPrice ? (
|
||||
<Button size={'sm'} onClick={onConfirmPay}>
|
||||
确认支付
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size={'sm'}
|
||||
isLoading={isLoading}
|
||||
onClick={() => {
|
||||
handleClickPay(Math.ceil(formatStorePrice2Read(payPrice - teamBalance)));
|
||||
}}
|
||||
>
|
||||
余额不足,去充值
|
||||
</Button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
|
||||
{!!qrPayData && <QRCodePayModal {...qrPayData} onSuccess={onConfirmPay} />}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,56 +3,48 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { Box, Image } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getTeamPlanStatus } from '@/web/support/wallet/sub/api';
|
||||
import { getTeamDatasetValidSub } from '@/web/support/wallet/sub/api';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import StandardPlan from './components/Standard';
|
||||
import ExtraPlan from './components/ExtraPlan';
|
||||
import PointsCard from './components/Points';
|
||||
import FAQ from './components/FAQ';
|
||||
import { getToken } from '@/web/support/user/auth';
|
||||
import Script from 'next/script';
|
||||
|
||||
const PriceBox = () => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const { data: teamSubPlan, refetch: refetchTeamSubPlan } = useQuery(
|
||||
['getTeamPlanStatus'],
|
||||
getTeamPlanStatus,
|
||||
['getTeamDatasetValidSub'],
|
||||
getTeamDatasetValidSub,
|
||||
{
|
||||
enabled: !!getToken() || !!userInfo
|
||||
enabled: !!userInfo
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
|
||||
<Box
|
||||
h={'100%'}
|
||||
overflow={'overlay'}
|
||||
w={'100%'}
|
||||
px={['20px', '5vw']}
|
||||
py={['30px', '80px']}
|
||||
backgroundImage={'url(/imgs/priceBg.svg)'}
|
||||
backgroundSize={'cover'}
|
||||
backgroundRepeat={'no-repeat'}
|
||||
>
|
||||
{/* standard sub */}
|
||||
<StandardPlan
|
||||
standardPlan={teamSubPlan?.standard}
|
||||
refetchTeamSubPlan={refetchTeamSubPlan}
|
||||
/>
|
||||
<Box
|
||||
h={'100%'}
|
||||
overflow={'overlay'}
|
||||
w={'100%'}
|
||||
px={['20px', '5vw']}
|
||||
py={['30px', '80px']}
|
||||
backgroundImage={'url(/imgs/priceBg.svg)'}
|
||||
backgroundSize={'cover'}
|
||||
backgroundRepeat={'no-repeat'}
|
||||
>
|
||||
{/* standard sub */}
|
||||
<StandardPlan standardPlan={teamSubPlan?.standard} refetchTeamSubPlan={refetchTeamSubPlan} />
|
||||
|
||||
<ExtraPlan />
|
||||
<ExtraPlan extraDatasetSize={teamSubPlan?.extraDatasetSize} />
|
||||
|
||||
{/* points */}
|
||||
<PointsCard />
|
||||
{/* points */}
|
||||
<PointsCard />
|
||||
|
||||
{/* question */}
|
||||
<FAQ />
|
||||
</Box>
|
||||
</>
|
||||
{/* question */}
|
||||
<FAQ />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -40,15 +40,6 @@ const Tools = () => {
|
||||
link: getDocPath('/docs/intro')
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(feConfigs?.show_pay
|
||||
? [
|
||||
{
|
||||
icon: 'support/bill/priceLight',
|
||||
label: '计费说明',
|
||||
link: '/price'
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
|
||||
17
projects/app/src/pages/tools/price.tsx
Normal file
17
projects/app/src/pages/tools/price.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import Price from '@/components/support/wallet/Price';
|
||||
import { useRouter } from 'next/router';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
|
||||
const PriceBox = () => {
|
||||
const router = useRouter();
|
||||
return <Price onClose={router.back} />;
|
||||
};
|
||||
|
||||
export default PriceBox;
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
return {
|
||||
props: { ...(await serviceSideProps(context)) }
|
||||
};
|
||||
}
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
} from '@fastgpt/global/core/dataset/controller';
|
||||
import {
|
||||
insertDatasetDataVector,
|
||||
recallFromVectorStore
|
||||
recallFromVectorStore,
|
||||
updateDatasetDataVector
|
||||
} from '@fastgpt/service/common/vectorStore/controller';
|
||||
import {
|
||||
DatasetDataIndexTypeEnum,
|
||||
DatasetSearchModeEnum,
|
||||
DatasetSearchModeMap,
|
||||
SearchScoreTypeEnum
|
||||
@@ -20,7 +22,6 @@ import { deleteDatasetDataVector } from '@fastgpt/service/common/vectorStore/con
|
||||
import { getVectorsByText } from '@fastgpt/service/core/ai/embedding';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import {
|
||||
DatasetDataItemType,
|
||||
DatasetDataSchemaType,
|
||||
DatasetDataWithCollectionType,
|
||||
SearchDataResponseItemType
|
||||
@@ -34,7 +35,7 @@ import type {
|
||||
} from '@fastgpt/global/core/dataset/api.d';
|
||||
import { pushDataListToTrainingQueue } from '@fastgpt/service/core/dataset/training/controller';
|
||||
import { getVectorModel } from '../../ai/model';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
export async function pushDataToTrainingQueue(
|
||||
props: {
|
||||
@@ -77,7 +78,7 @@ export async function insertData2Dataset({
|
||||
return Promise.reject("teamId and tmbId can't be the same");
|
||||
}
|
||||
|
||||
const qaStr = getDefaultIndex({ q, a }).text;
|
||||
const qaStr = `${q}\n${a}`.trim();
|
||||
|
||||
// empty indexes check, if empty, create default index
|
||||
indexes =
|
||||
@@ -85,16 +86,10 @@ export async function insertData2Dataset({
|
||||
? indexes.map((index) => ({
|
||||
...index,
|
||||
dataId: undefined,
|
||||
defaultIndex: index.text.trim() === qaStr
|
||||
defaultIndex: indexes?.length === 1 && index.text === qaStr ? true : index.defaultIndex
|
||||
}))
|
||||
: [getDefaultIndex({ q, a })];
|
||||
|
||||
if (!indexes.find((index) => index.defaultIndex)) {
|
||||
indexes.unshift(getDefaultIndex({ q, a }));
|
||||
}
|
||||
|
||||
indexes = indexes.slice(0, 6);
|
||||
|
||||
// insert to vector store
|
||||
const result = await Promise.all(
|
||||
indexes.map((item) =>
|
||||
@@ -133,10 +128,8 @@ export async function insertData2Dataset({
|
||||
/**
|
||||
* update data
|
||||
* 1. compare indexes
|
||||
* 2. insert new pg data
|
||||
* session run:
|
||||
* 3. update mongo data(session run)
|
||||
* 4. delete old pg data
|
||||
* 2. update pg data
|
||||
* 3. update mongo data
|
||||
*/
|
||||
export async function updateData2Dataset({
|
||||
dataId,
|
||||
@@ -148,30 +141,31 @@ export async function updateData2Dataset({
|
||||
if (!Array.isArray(indexes)) {
|
||||
return Promise.reject('indexes is required');
|
||||
}
|
||||
const qaStr = getDefaultIndex({ q, a }).text;
|
||||
const qaStr = `${q}\n${a}`.trim();
|
||||
|
||||
// patch index and update pg
|
||||
const mongoData = await MongoDatasetData.findById(dataId);
|
||||
if (!mongoData) return Promise.reject('core.dataset.error.Data not found');
|
||||
|
||||
// remove defaultIndex
|
||||
let formatIndexes = indexes.map((index) => ({
|
||||
...index,
|
||||
text: index.text.trim(),
|
||||
defaultIndex: index.text.trim() === qaStr
|
||||
}));
|
||||
if (!formatIndexes.find((index) => index.defaultIndex)) {
|
||||
const defaultIndex = mongoData.indexes.find((index) => index.defaultIndex);
|
||||
formatIndexes.unshift(defaultIndex ? defaultIndex : getDefaultIndex({ q, a }));
|
||||
// make sure have one index
|
||||
if (indexes.length === 0) {
|
||||
const databaseDefaultIndex = mongoData.indexes.find((index) => index.defaultIndex);
|
||||
|
||||
indexes = [
|
||||
getDefaultIndex({
|
||||
q,
|
||||
a,
|
||||
dataId: databaseDefaultIndex ? String(databaseDefaultIndex.dataId) : undefined
|
||||
})
|
||||
];
|
||||
}
|
||||
formatIndexes = formatIndexes.slice(0, 6);
|
||||
|
||||
// patch indexes, create, update, delete
|
||||
const patchResult: PatchIndexesProps[] = [];
|
||||
|
||||
// find database indexes in new Indexes, if have not, delete it
|
||||
for (const item of mongoData.indexes) {
|
||||
const index = formatIndexes.find((index) => index.dataId === item.dataId);
|
||||
const index = indexes.find((index) => index.dataId === item.dataId);
|
||||
if (!index) {
|
||||
patchResult.push({
|
||||
type: 'delete',
|
||||
@@ -179,34 +173,35 @@ export async function updateData2Dataset({
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const item of formatIndexes) {
|
||||
for (const item of indexes) {
|
||||
const index = mongoData.indexes.find((index) => index.dataId === item.dataId);
|
||||
// in database, update
|
||||
if (index) {
|
||||
// default index update
|
||||
if (index.defaultIndex && index.text !== qaStr) {
|
||||
patchResult.push({
|
||||
type: 'update',
|
||||
index: {
|
||||
//@ts-ignore
|
||||
...index.toObject(),
|
||||
text: qaStr
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// custom index update
|
||||
// manual update index
|
||||
if (index.text !== item.text) {
|
||||
patchResult.push({
|
||||
type: 'update',
|
||||
index: item
|
||||
});
|
||||
continue;
|
||||
} else if (index.defaultIndex && index.text !== qaStr) {
|
||||
// update default index
|
||||
patchResult.push({
|
||||
type: 'update',
|
||||
index: {
|
||||
...item,
|
||||
type:
|
||||
item.type === DatasetDataIndexTypeEnum.qa && !a
|
||||
? DatasetDataIndexTypeEnum.chunk
|
||||
: item.type,
|
||||
text: qaStr
|
||||
}
|
||||
});
|
||||
} else {
|
||||
patchResult.push({
|
||||
type: 'unChange',
|
||||
index: item
|
||||
});
|
||||
}
|
||||
patchResult.push({
|
||||
type: 'unChange',
|
||||
index: item
|
||||
});
|
||||
} else {
|
||||
// not in database, create
|
||||
patchResult.push({
|
||||
@@ -220,12 +215,10 @@ export async function updateData2Dataset({
|
||||
mongoData.updateTime = new Date();
|
||||
await mongoData.save();
|
||||
|
||||
// insert vector
|
||||
const clonePatchResult2Insert: PatchIndexesProps[] = JSON.parse(JSON.stringify(patchResult));
|
||||
const insertResult = await Promise.all(
|
||||
clonePatchResult2Insert.map(async (item) => {
|
||||
// insert new vector and update dateId
|
||||
if (item.type === 'create' || item.type === 'update') {
|
||||
// update vector
|
||||
const result = await Promise.all(
|
||||
patchResult.map(async (item) => {
|
||||
if (item.type === 'create') {
|
||||
const result = await insertDatasetDataVector({
|
||||
query: item.index.text,
|
||||
model: getVectorModel(model),
|
||||
@@ -236,54 +229,50 @@ export async function updateData2Dataset({
|
||||
item.index.dataId = result.insertId;
|
||||
return result;
|
||||
}
|
||||
if (item.type === 'update' && item.index.dataId) {
|
||||
const result = await updateDatasetDataVector({
|
||||
teamId: mongoData.teamId,
|
||||
datasetId: mongoData.datasetId,
|
||||
collectionId: mongoData.collectionId,
|
||||
id: item.index.dataId,
|
||||
query: item.index.text,
|
||||
model: getVectorModel(model)
|
||||
});
|
||||
item.index.dataId = result.insertId;
|
||||
|
||||
return result;
|
||||
}
|
||||
if (item.type === 'delete' && item.index.dataId) {
|
||||
await deleteDatasetDataVector({
|
||||
teamId: mongoData.teamId,
|
||||
id: item.index.dataId
|
||||
});
|
||||
return {
|
||||
charsLength: 0
|
||||
};
|
||||
}
|
||||
return {
|
||||
charsLength: 0
|
||||
};
|
||||
})
|
||||
);
|
||||
const charsLength = insertResult.reduce((acc, cur) => acc + cur.charsLength, 0);
|
||||
// console.log(clonePatchResult2Insert);
|
||||
await mongoSessionRun(async (session) => {
|
||||
// update mongo
|
||||
const newIndexes = clonePatchResult2Insert
|
||||
.filter((item) => item.type !== 'delete')
|
||||
.map((item) => item.index);
|
||||
// update mongo other data
|
||||
mongoData.q = q || mongoData.q;
|
||||
mongoData.a = a ?? mongoData.a;
|
||||
mongoData.fullTextToken = jiebaSplit({ text: mongoData.q + mongoData.a });
|
||||
// @ts-ignore
|
||||
mongoData.indexes = newIndexes;
|
||||
await mongoData.save({ session });
|
||||
|
||||
// delete vector
|
||||
const deleteIdList = patchResult
|
||||
.filter((item) => item.type === 'delete' || item.type === 'update')
|
||||
.map((item) => item.index.dataId)
|
||||
.filter(Boolean);
|
||||
if (deleteIdList.length > 0) {
|
||||
await deleteDatasetDataVector({
|
||||
teamId: mongoData.teamId,
|
||||
idList: deleteIdList as string[]
|
||||
});
|
||||
}
|
||||
});
|
||||
const charsLength = result.reduce((acc, cur) => acc + cur.charsLength, 0);
|
||||
const newIndexes = patchResult.filter((item) => item.type !== 'delete').map((item) => item.index);
|
||||
|
||||
// update mongo other data
|
||||
mongoData.q = q || mongoData.q;
|
||||
mongoData.a = a ?? mongoData.a;
|
||||
mongoData.fullTextToken = jiebaSplit({ text: mongoData.q + mongoData.a });
|
||||
// @ts-ignore
|
||||
mongoData.indexes = newIndexes;
|
||||
await mongoData.save();
|
||||
|
||||
return {
|
||||
charsLength
|
||||
};
|
||||
}
|
||||
|
||||
export const deleteDatasetData = async (data: DatasetDataItemType) => {
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoDatasetData.findByIdAndDelete(data.id, { session });
|
||||
await deleteDatasetDataVector({
|
||||
teamId: data.teamId,
|
||||
idList: data.indexes.map((item) => item.dataId)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
type SearchDatasetDataProps = {
|
||||
teamId: string;
|
||||
model: string;
|
||||
@@ -388,7 +377,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
a: data.a,
|
||||
chunkIndex: data.chunkIndex,
|
||||
datasetId: String(data.datasetId),
|
||||
collectionId: String(data.collectionId?._id),
|
||||
collectionId: String(data.collectionId._id),
|
||||
sourceName: data.collectionId.name || '',
|
||||
sourceId: data.collectionId?.fileId || data.collectionId?.rawLink,
|
||||
score: [{ type: SearchScoreTypeEnum.embedding, value: data.score, index }]
|
||||
@@ -492,7 +481,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
}))
|
||||
});
|
||||
|
||||
if (results.length === 0) {
|
||||
if (!Array.isArray(results)) {
|
||||
usingReRank = false;
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { pushQAUsage } from '@/service/support/wallet/usage/push';
|
||||
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { pushQABill } from '@/service/support/wallet/bill/push';
|
||||
import { DatasetDataIndexTypeEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { sendOneInform } from '../support/user/inform/api';
|
||||
import { getAIApi } from '@fastgpt/service/core/ai/config';
|
||||
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
|
||||
@@ -9,12 +9,12 @@ import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { Prompt_AgentQA } from '@/global/core/prompt/agent';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { authTeamBalance } from '../support/permission/auth/bill';
|
||||
import type { PushDatasetDataChunkProps } from '@fastgpt/global/core/dataset/api.d';
|
||||
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
|
||||
import { lockTrainingDataByTeamId } from '@fastgpt/service/core/dataset/training/controller';
|
||||
import { pushDataToTrainingQueue } from '@/service/core/dataset/data/controller';
|
||||
import { getLLMModel } from '../core/ai/model';
|
||||
import { checkTeamAIPoints } from '../support/permission/teamLimit';
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
|
||||
const reduceQueue = () => {
|
||||
global.qaQueueLen = global.qaQueueLen > 0 ? global.qaQueueLen - 1 : 0;
|
||||
@@ -89,16 +89,16 @@ export async function generateQA(): Promise<any> {
|
||||
|
||||
// auth balance
|
||||
try {
|
||||
await checkTeamAIPoints(data.teamId);
|
||||
await authTeamBalance(data.teamId);
|
||||
} catch (error: any) {
|
||||
if (error?.statusText === TeamErrEnum.aiPointsNotEnough) {
|
||||
if (error?.statusText === UserErrEnum.balanceNotEnough) {
|
||||
// send inform and lock data
|
||||
try {
|
||||
sendOneInform({
|
||||
type: 'system',
|
||||
title: '文本训练任务中止',
|
||||
content:
|
||||
'该团队账号的AI积分不足,文本训练任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
|
||||
'该团队账号余额不足,文本训练任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
|
||||
tmbId: data.tmbId
|
||||
});
|
||||
console.log('余额不足,暂停【QA】生成任务');
|
||||
@@ -161,7 +161,7 @@ ${replaceVariable(Prompt_AgentQA.fixedText, { text })}`;
|
||||
|
||||
// add bill
|
||||
if (insertLen > 0) {
|
||||
pushQAUsage({
|
||||
pushQABill({
|
||||
teamId: data.teamId,
|
||||
tmbId: data.tmbId,
|
||||
charsLength: `${prompt}${answer}`.length,
|
||||
@@ -230,6 +230,7 @@ function formatSplitText(text: string, rawText: string) {
|
||||
indexes: [
|
||||
{
|
||||
defaultIndex: true,
|
||||
type: DatasetDataIndexTypeEnum.qa,
|
||||
text: `${q}\n${a.trim().replace(/\n\s*/g, '\n')}`
|
||||
}
|
||||
]
|
||||
@@ -247,6 +248,7 @@ function formatSplitText(text: string, rawText: string) {
|
||||
indexes: [
|
||||
{
|
||||
defaultIndex: true,
|
||||
type: DatasetDataIndexTypeEnum.chunk,
|
||||
text: chunk
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,10 +4,10 @@ import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { sendOneInform } from '../support/user/inform/api';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { checkTeamAIPoints } from '../support/permission/teamLimit';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { authTeamBalance } from '@/service/support/permission/auth/bill';
|
||||
import { pushGenerateVectorBill } from '@/service/support/wallet/bill/push';
|
||||
import { UserErrEnum } from '@fastgpt/global/common/error/code/user';
|
||||
import { lockTrainingDataByTeamId } from '@fastgpt/service/core/dataset/training/controller';
|
||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||
|
||||
const reduceQueue = () => {
|
||||
global.vectorQueueLen = global.vectorQueueLen > 0 ? global.vectorQueueLen - 1 : 0;
|
||||
@@ -93,16 +93,16 @@ export async function generateVector(): Promise<any> {
|
||||
|
||||
// auth balance
|
||||
try {
|
||||
await checkTeamAIPoints(data.teamId);
|
||||
await authTeamBalance(data.teamId);
|
||||
} catch (error: any) {
|
||||
if (error?.statusText === TeamErrEnum.aiPointsNotEnough) {
|
||||
if (error?.statusText === UserErrEnum.balanceNotEnough) {
|
||||
// send inform and lock data
|
||||
try {
|
||||
sendOneInform({
|
||||
type: 'system',
|
||||
title: '文本训练任务中止',
|
||||
content:
|
||||
'该团队账号AI积分不足,文本训练任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
|
||||
'该团队账号余额不足,文本训练任务中止,重新充值后将会继续。暂停的任务将在 7 天后被删除。',
|
||||
tmbId: data.tmbId
|
||||
});
|
||||
console.log('余额不足,暂停【向量】生成任务');
|
||||
@@ -138,7 +138,7 @@ export async function generateVector(): Promise<any> {
|
||||
});
|
||||
|
||||
// push bill
|
||||
pushGenerateVectorUsage({
|
||||
pushGenerateVectorBill({
|
||||
teamId: data.teamId,
|
||||
tmbId: data.tmbId,
|
||||
charsLength,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
|
||||
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
|
||||
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getAIApi } from '@fastgpt/service/core/ai/config';
|
||||
import type {
|
||||
ClassifyQuestionAgentItemType,
|
||||
ModuleDispatchResponse
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
@@ -14,7 +11,7 @@ import { Prompt_CQJson } from '@/global/core/prompt/agent';
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { ModelTypeEnum, getLLMModel } from '@/service/core/ai/model';
|
||||
import { getHistories } from '../utils';
|
||||
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
|
||||
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
@@ -23,9 +20,10 @@ type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
[ModuleInputKeyEnum.agents]: ClassifyQuestionAgentItemType[];
|
||||
}>;
|
||||
type CQResponse = ModuleDispatchResponse<{
|
||||
type CQResponse = {
|
||||
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
};
|
||||
|
||||
const agentFunName = 'classify_question';
|
||||
|
||||
@@ -33,7 +31,6 @@ const agentFunName = 'classify_question';
|
||||
export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse> => {
|
||||
const {
|
||||
user,
|
||||
module: { name },
|
||||
histories,
|
||||
params: { model, history = 6, agents, userChatInput }
|
||||
} = props as Props;
|
||||
@@ -46,7 +43,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { arg, charsLength } = await (async () => {
|
||||
const { arg, inputTokens, outputTokens } = await (async () => {
|
||||
if (cqModel.toolChoice) {
|
||||
return toolChoice({
|
||||
...props,
|
||||
@@ -63,31 +60,25 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
|
||||
const result = agents.find((item) => item.key === arg?.type) || agents[agents.length - 1];
|
||||
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: cqModel.model,
|
||||
charsLength,
|
||||
modelType: ModelTypeEnum.llm
|
||||
inputLen: inputTokens,
|
||||
outputLen: outputTokens,
|
||||
type: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
return {
|
||||
[result.key]: true,
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
||||
price: user.openaiAccount?.key ? 0 : total,
|
||||
model: modelName,
|
||||
query: userChatInput,
|
||||
charsLength,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cqList: agents,
|
||||
cqResult: result.value,
|
||||
contextTotalLen: chatHistories.length + 2
|
||||
},
|
||||
[ModuleOutputKeyEnum.moduleDispatchBills]: [
|
||||
{
|
||||
moduleName: name,
|
||||
totalPoints,
|
||||
model: modelName,
|
||||
charsLength
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -158,13 +149,11 @@ ${systemPrompt}
|
||||
const arg = JSON.parse(
|
||||
response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || ''
|
||||
);
|
||||
const functionChars =
|
||||
agentFunction.description.length +
|
||||
agentFunction.parameters.properties.type.description.length;
|
||||
|
||||
return {
|
||||
arg,
|
||||
charsLength: countMessagesChars(messages) + functionChars
|
||||
inputTokens: response.usage?.prompt_tokens || 0,
|
||||
outputTokens: response.usage?.completion_tokens || 0
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(agentFunction.parameters);
|
||||
@@ -174,7 +163,8 @@ ${systemPrompt}
|
||||
|
||||
return {
|
||||
arg: {},
|
||||
charsLength: 0
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -216,7 +206,8 @@ async function completions({
|
||||
agents.find((item) => answer.includes(item.key) || answer.includes(item.value))?.key || '';
|
||||
|
||||
return {
|
||||
charsLength: countMessagesChars(messages),
|
||||
inputTokens: data.usage?.prompt_tokens || 0,
|
||||
outputTokens: data.usage?.completion_tokens || 0,
|
||||
arg: { type: id }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
|
||||
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
|
||||
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getAIApi } from '@fastgpt/service/core/ai/config';
|
||||
import type {
|
||||
ContextExtractAgentItemType,
|
||||
ModuleDispatchResponse
|
||||
} from '@fastgpt/global/core/module/type';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/module/type';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { Prompt_ExtractJson } from '@/global/core/prompt/agent';
|
||||
@@ -14,7 +11,7 @@ import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { getHistories } from '../utils';
|
||||
import { ModelTypeEnum, getLLMModel } from '@/service/core/ai/model';
|
||||
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
|
||||
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.history]?: ChatItemType[];
|
||||
@@ -23,18 +20,18 @@ type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.description]: string;
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
}>;
|
||||
type Response = ModuleDispatchResponse<{
|
||||
type Response = {
|
||||
[ModuleOutputKeyEnum.success]?: boolean;
|
||||
[ModuleOutputKeyEnum.failed]?: boolean;
|
||||
[ModuleOutputKeyEnum.contextExtractFields]: string;
|
||||
}>;
|
||||
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
|
||||
};
|
||||
|
||||
const agentFunName = 'extract_json_data';
|
||||
|
||||
export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
const {
|
||||
user,
|
||||
module: { name },
|
||||
histories,
|
||||
params: { content, history = 6, model, description, extractKeys }
|
||||
} = props;
|
||||
@@ -46,7 +43,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
const extractModel = getLLMModel(model);
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { arg, charsLength } = await (async () => {
|
||||
const { arg, inputTokens, outputTokens } = await (async () => {
|
||||
if (extractModel.toolChoice) {
|
||||
return toolChoice({
|
||||
...props,
|
||||
@@ -83,10 +80,11 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
}
|
||||
}
|
||||
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: extractModel.model,
|
||||
charsLength,
|
||||
modelType: ModelTypeEnum.llm
|
||||
inputLen: inputTokens,
|
||||
outputLen: outputTokens,
|
||||
type: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -95,22 +93,15 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
[ModuleOutputKeyEnum.contextExtractFields]: JSON.stringify(arg),
|
||||
...arg,
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
||||
price: user.openaiAccount?.key ? 0 : total,
|
||||
model: modelName,
|
||||
query: content,
|
||||
charsLength,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
extractDescription: description,
|
||||
extractResult: arg,
|
||||
contextTotalLen: chatHistories.length + 2
|
||||
},
|
||||
[ModuleOutputKeyEnum.moduleDispatchBills]: [
|
||||
{
|
||||
moduleName: name,
|
||||
totalPoints,
|
||||
model: modelName,
|
||||
charsLength
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -202,12 +193,10 @@ ${description || '根据用户要求获取适当的 JSON 字符串。'}
|
||||
}
|
||||
})();
|
||||
|
||||
const functionChars =
|
||||
description.length + extractKeys.reduce((sum, item) => sum + item.desc.length, 0);
|
||||
|
||||
return {
|
||||
rawResponse: response?.choices?.[0]?.message?.tool_calls?.[0]?.function?.arguments || '',
|
||||
charsLength: countMessagesChars(messages) + functionChars,
|
||||
inputTokens: response.usage?.prompt_tokens || 0,
|
||||
outputTokens: response.usage?.completion_tokens || 0,
|
||||
arg
|
||||
};
|
||||
}
|
||||
@@ -249,6 +238,8 @@ Human: ${content}`
|
||||
stream: false
|
||||
});
|
||||
const answer = data.choices?.[0].message?.content || '';
|
||||
const inputTokens = data.usage?.prompt_tokens || 0;
|
||||
const outputTokens = data.usage?.completion_tokens || 0;
|
||||
|
||||
// parse response
|
||||
const start = answer.indexOf('{');
|
||||
@@ -257,7 +248,8 @@ Human: ${content}`
|
||||
if (start === -1 || end === -1)
|
||||
return {
|
||||
rawResponse: answer,
|
||||
charsLength: countMessagesChars(messages),
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
arg: {}
|
||||
};
|
||||
|
||||
@@ -269,14 +261,15 @@ Human: ${content}`
|
||||
try {
|
||||
return {
|
||||
rawResponse: answer,
|
||||
charsLength: countMessagesChars(messages),
|
||||
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
arg: JSON.parse(jsonStr) as Record<string, any>
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
rawResponse: answer,
|
||||
charsLength: countMessagesChars(messages),
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
arg: {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { ChatContextFilter, countMessagesChars } from '@fastgpt/service/core/chat/utils';
|
||||
import { ChatContextFilter } from '@fastgpt/service/core/chat/utils';
|
||||
import type { moduleDispatchResType, ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
|
||||
import { textAdaptGptResponse } from '@/utils/adapt';
|
||||
import { getAIApi } from '@fastgpt/service/core/ai/config';
|
||||
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
|
||||
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
|
||||
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { postTextCensor } from '@/service/common/censor';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constant';
|
||||
import type { ModuleDispatchResponse, ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { countMessagesTokens, sliceMessagesTB } from '@fastgpt/global/common/string/tiktoken';
|
||||
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { Prompt_QuotePromptList, Prompt_QuoteTemplateList } from '@/global/core/prompt/AIChat';
|
||||
@@ -32,10 +32,11 @@ export type ChatProps = ModuleDispatchProps<
|
||||
[ModuleInputKeyEnum.aiChatDatasetQuote]?: SearchDataResponseItemType[];
|
||||
}
|
||||
>;
|
||||
export type ChatResponse = ModuleDispatchResponse<{
|
||||
export type ChatResponse = {
|
||||
[ModuleOutputKeyEnum.answerText]: string;
|
||||
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
|
||||
[ModuleOutputKeyEnum.history]: ChatItemType[];
|
||||
}>;
|
||||
};
|
||||
|
||||
/* request openai chat */
|
||||
export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResponse> => {
|
||||
@@ -45,7 +46,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
detail = false,
|
||||
user,
|
||||
histories,
|
||||
module: { name, outputs },
|
||||
outputs,
|
||||
params: {
|
||||
model,
|
||||
temperature = 0,
|
||||
@@ -153,7 +154,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
}
|
||||
);
|
||||
|
||||
const { answerText, completeMessages } = await (async () => {
|
||||
const { answerText, inputTokens, outputTokens, completeMessages } = await (async () => {
|
||||
if (stream) {
|
||||
// sse response
|
||||
const { answer } = await streamResponse({
|
||||
@@ -171,6 +172,17 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
|
||||
return {
|
||||
answerText: answer,
|
||||
inputTokens: countMessagesTokens({
|
||||
messages: filterMessages
|
||||
}),
|
||||
outputTokens: countMessagesTokens({
|
||||
messages: [
|
||||
{
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: answer
|
||||
}
|
||||
]
|
||||
}),
|
||||
completeMessages
|
||||
};
|
||||
} else {
|
||||
@@ -184,38 +196,33 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
|
||||
return {
|
||||
answerText: answer,
|
||||
inputTokens: unStreamResponse.usage?.prompt_tokens || 0,
|
||||
outputTokens: unStreamResponse.usage?.completion_tokens || 0,
|
||||
completeMessages
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
const charsLength = countMessagesChars(completeMessages);
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model,
|
||||
charsLength,
|
||||
modelType: ModelTypeEnum.llm
|
||||
inputLen: inputTokens,
|
||||
outputLen: outputTokens,
|
||||
type: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
return {
|
||||
answerText,
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
||||
responseData: {
|
||||
price: user.openaiAccount?.key ? 0 : total,
|
||||
model: modelName,
|
||||
charsLength,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
query: `${userChatInput}`,
|
||||
maxToken: max_tokens,
|
||||
quoteList: filterQuoteQA,
|
||||
historyPreview: getHistoryPreview(completeMessages),
|
||||
contextTotalLen: completeMessages.length
|
||||
},
|
||||
[ModuleOutputKeyEnum.moduleDispatchBills]: [
|
||||
{
|
||||
moduleName: name,
|
||||
totalPoints,
|
||||
model: modelName,
|
||||
charsLength
|
||||
}
|
||||
],
|
||||
history: completeMessages
|
||||
};
|
||||
};
|
||||
@@ -242,13 +249,30 @@ function filterQuote({
|
||||
// slice filterSearch
|
||||
const filterQuoteQA = filterSearchResultsByMaxChars(quoteQA, model.quoteMaxToken);
|
||||
|
||||
// filterQuoteQA按collectionId聚合在一起后,再按chunkIndex从小到大排序
|
||||
const sortQuoteQAMap: Record<string, SearchDataResponseItemType[]> = {};
|
||||
filterQuoteQA.forEach((item) => {
|
||||
if (sortQuoteQAMap[item.collectionId]) {
|
||||
sortQuoteQAMap[item.collectionId].push(item);
|
||||
} else {
|
||||
sortQuoteQAMap[item.collectionId] = [item];
|
||||
}
|
||||
});
|
||||
const sortQuoteQAList = Object.values(sortQuoteQAMap);
|
||||
|
||||
sortQuoteQAList.forEach((qaList) => {
|
||||
qaList.sort((a, b) => a.chunkIndex - b.chunkIndex);
|
||||
});
|
||||
|
||||
const flatQuoteList = sortQuoteQAList.flat();
|
||||
|
||||
const quoteText =
|
||||
filterQuoteQA.length > 0
|
||||
? `${filterQuoteQA.map((item, index) => getValue(item, index).trim()).join('\n------\n')}`
|
||||
flatQuoteList.length > 0
|
||||
? `${flatQuoteList.map((item, index) => getValue(item, index)).join('\n')}`
|
||||
: '';
|
||||
|
||||
return {
|
||||
filterQuoteQA: filterQuoteQA,
|
||||
filterQuoteQA: flatQuoteList,
|
||||
quoteText
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { formatModelChars2Points } from '@/service/support/wallet/usage/utils';
|
||||
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import type {
|
||||
ModuleDispatchProps,
|
||||
ModuleDispatchResponse
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModelTypeEnum, getLLMModel, getVectorModel } from '@/service/core/ai/model';
|
||||
import { searchDatasetData } from '@/service/core/dataset/data/controller';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { queryExtension } from '@fastgpt/service/core/ai/functions/queryExtension';
|
||||
import { getHistories } from '../utils';
|
||||
import { datasetSearchQueryExtension } from '@fastgpt/service/core/dataset/search/utils';
|
||||
import { ChatModuleBillType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { checkTeamReRankPermission } from '@/service/support/permission/teamLimit';
|
||||
|
||||
type DatasetSearchProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
||||
@@ -26,11 +22,12 @@ type DatasetSearchProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.datasetSearchExtensionModel]: string;
|
||||
[ModuleInputKeyEnum.datasetSearchExtensionBg]: string;
|
||||
}>;
|
||||
export type DatasetSearchResponse = ModuleDispatchResponse<{
|
||||
export type DatasetSearchResponse = {
|
||||
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
|
||||
[ModuleOutputKeyEnum.datasetIsEmpty]?: boolean;
|
||||
[ModuleOutputKeyEnum.datasetUnEmpty]?: boolean;
|
||||
[ModuleOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
|
||||
}>;
|
||||
};
|
||||
|
||||
export async function dispatchDatasetSearch(
|
||||
props: DatasetSearchProps
|
||||
@@ -38,7 +35,6 @@ export async function dispatchDatasetSearch(
|
||||
const {
|
||||
teamId,
|
||||
histories,
|
||||
module,
|
||||
params: {
|
||||
datasets = [],
|
||||
similarity,
|
||||
@@ -77,8 +73,6 @@ export async function dispatchDatasetSearch(
|
||||
histories: getHistories(6, histories)
|
||||
});
|
||||
|
||||
// console.log(concatQueries, rewriteQuery, aiExtensionResult);
|
||||
|
||||
// get vector
|
||||
const vectorModel = getVectorModel(datasets[0]?.vectorModel?.model);
|
||||
|
||||
@@ -97,18 +91,18 @@ export async function dispatchDatasetSearch(
|
||||
limit,
|
||||
datasetIds: datasets.map((item) => item.datasetId),
|
||||
searchMode,
|
||||
usingReRank: usingReRank && (await checkTeamReRankPermission(teamId))
|
||||
usingReRank
|
||||
});
|
||||
|
||||
// count bill results
|
||||
// vector
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: vectorModel.model,
|
||||
charsLength,
|
||||
modelType: ModelTypeEnum.vector
|
||||
inputLen: charsLength,
|
||||
type: ModelTypeEnum.vector
|
||||
});
|
||||
const responseData: moduleDispatchResType & { totalPoints: number } = {
|
||||
totalPoints,
|
||||
const responseData: moduleDispatchResType & { price: number } = {
|
||||
price: total,
|
||||
query: concatQueries.join('\n'),
|
||||
model: modelName,
|
||||
charsLength,
|
||||
@@ -117,42 +111,28 @@ export async function dispatchDatasetSearch(
|
||||
searchMode,
|
||||
searchUsingReRank: searchUsingReRank
|
||||
};
|
||||
const moduleDispatchBills: ChatModuleBillType[] = [
|
||||
{
|
||||
totalPoints,
|
||||
moduleName: module.name,
|
||||
model: modelName,
|
||||
charsLength
|
||||
}
|
||||
];
|
||||
|
||||
if (aiExtensionResult) {
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: aiExtensionResult.model,
|
||||
charsLength: aiExtensionResult.charsLength,
|
||||
modelType: ModelTypeEnum.llm
|
||||
inputLen: aiExtensionResult.inputTokens,
|
||||
outputLen: aiExtensionResult.outputTokens,
|
||||
type: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
responseData.totalPoints += totalPoints;
|
||||
responseData.charsLength = aiExtensionResult.charsLength;
|
||||
responseData.price += total;
|
||||
responseData.inputTokens = aiExtensionResult.inputTokens;
|
||||
responseData.outputTokens = aiExtensionResult.outputTokens;
|
||||
responseData.extensionModel = modelName;
|
||||
responseData.extensionResult =
|
||||
aiExtensionResult.extensionQueries?.join('\n') ||
|
||||
JSON.stringify(aiExtensionResult.extensionQueries);
|
||||
|
||||
moduleDispatchBills.push({
|
||||
totalPoints,
|
||||
moduleName: 'core.module.template.Query extension',
|
||||
model: modelName,
|
||||
charsLength: aiExtensionResult.charsLength
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
isEmpty: searchRes.length === 0 ? true : undefined,
|
||||
unEmpty: searchRes.length > 0 ? true : undefined,
|
||||
quoteQA: searchRes,
|
||||
responseData,
|
||||
moduleDispatchBills
|
||||
responseData
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,12 +23,11 @@ import { dispatchContentExtract } from './agent/extract';
|
||||
import { dispatchHttpRequest } from './tools/http';
|
||||
import { dispatchHttp468Request } from './tools/http468';
|
||||
import { dispatchAppRequest } from './tools/runApp';
|
||||
import { dispatchQueryExtension } from './tools/queryExternsion';
|
||||
import { dispatchCFR } from './tools/cfr';
|
||||
import { dispatchRunPlugin } from './plugin/run';
|
||||
import { dispatchPluginInput } from './plugin/runInput';
|
||||
import { dispatchPluginOutput } from './plugin/runOutput';
|
||||
import { valueTypeFormat } from './utils';
|
||||
import { ChatModuleBillType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
|
||||
const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
|
||||
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
|
||||
@@ -45,7 +44,7 @@ const callbackMap: Record<`${FlowNodeTypeEnum}`, Function> = {
|
||||
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
|
||||
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
|
||||
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput,
|
||||
[FlowNodeTypeEnum.queryExtension]: dispatchQueryExtension,
|
||||
[FlowNodeTypeEnum.cfr]: dispatchCFR,
|
||||
|
||||
// none
|
||||
[FlowNodeTypeEnum.userGuide]: () => Promise.resolve()
|
||||
@@ -83,19 +82,16 @@ export async function dispatchModules({
|
||||
// let storeData: Record<string, any> = {}; // after module used
|
||||
let chatResponse: ChatHistoryItemResType[] = []; // response request and save to database
|
||||
let chatAnswerText = ''; // AI answer
|
||||
let chatModuleBills: ChatModuleBillType[] = [];
|
||||
let runningTime = Date.now();
|
||||
|
||||
function pushStore(
|
||||
{ inputs = [] }: RunningModuleItemType,
|
||||
{
|
||||
answerText = '',
|
||||
responseData,
|
||||
moduleDispatchBills
|
||||
responseData
|
||||
}: {
|
||||
answerText?: string;
|
||||
responseData?: ChatHistoryItemResType | ChatHistoryItemResType[];
|
||||
moduleDispatchBills?: ChatModuleBillType[];
|
||||
}
|
||||
) {
|
||||
const time = Date.now();
|
||||
@@ -109,9 +105,6 @@ export async function dispatchModules({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (moduleDispatchBills) {
|
||||
chatModuleBills = chatModuleBills.concat(moduleDispatchBills);
|
||||
}
|
||||
runningTime = time;
|
||||
|
||||
const isResponseAnswerText =
|
||||
@@ -165,7 +158,6 @@ export async function dispatchModules({
|
||||
const filterModules = nextRunModules.filter((module) => {
|
||||
if (set.has(module.moduleId)) return false;
|
||||
set.add(module.moduleId);
|
||||
``;
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -207,7 +199,8 @@ export async function dispatchModules({
|
||||
user,
|
||||
stream,
|
||||
detail,
|
||||
module,
|
||||
outputs: module.outputs,
|
||||
inputs: module.inputs,
|
||||
params
|
||||
};
|
||||
|
||||
@@ -244,11 +237,10 @@ export async function dispatchModules({
|
||||
? params[ModuleOutputKeyEnum.userChatInput]
|
||||
: undefined,
|
||||
...dispatchRes,
|
||||
[ModuleOutputKeyEnum.responseData]: formatResponseData,
|
||||
[ModuleOutputKeyEnum.moduleDispatchBills]:
|
||||
dispatchRes[ModuleOutputKeyEnum.moduleDispatchBills]
|
||||
[ModuleOutputKeyEnum.responseData]: formatResponseData
|
||||
});
|
||||
}
|
||||
|
||||
// start process width initInput
|
||||
const initModules = runningModules.filter((item) => initRunningModuleType[item.flowType]);
|
||||
|
||||
@@ -274,8 +266,7 @@ export async function dispatchModules({
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.answerText]: chatAnswerText,
|
||||
[ModuleOutputKeyEnum.responseData]: chatResponse,
|
||||
[ModuleOutputKeyEnum.moduleDispatchBills]: chatModuleBills
|
||||
[ModuleOutputKeyEnum.responseData]: chatResponse
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import type {
|
||||
ModuleDispatchProps,
|
||||
ModuleDispatchResponse
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { dispatchModules } from '../index';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import {
|
||||
@@ -17,9 +14,10 @@ type RunPluginProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.pluginId]: string;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
type RunPluginResponse = ModuleDispatchResponse<{
|
||||
type RunPluginResponse = {
|
||||
[ModuleOutputKeyEnum.answerText]: string;
|
||||
}>;
|
||||
[ModuleOutputKeyEnum.responseData]?: moduleDispatchResType;
|
||||
};
|
||||
|
||||
export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPluginResponse> => {
|
||||
const {
|
||||
@@ -60,7 +58,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
||||
return params;
|
||||
})();
|
||||
|
||||
const { responseData, moduleDispatchBills, answerText } = await dispatchModules({
|
||||
const { responseData, answerText } = await dispatchModules({
|
||||
...props,
|
||||
modules: plugin.modules.map((module) => ({
|
||||
...module,
|
||||
@@ -78,9 +76,9 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
||||
return {
|
||||
answerText,
|
||||
// responseData, // debug
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
responseData: {
|
||||
moduleLogo: plugin.avatar,
|
||||
totalPoints: responseData.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
|
||||
price: responseData.reduce((sum, item) => sum + (item.price || 0), 0),
|
||||
runningTime: responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0),
|
||||
pluginOutput: output?.pluginOutput,
|
||||
pluginDetail:
|
||||
@@ -91,14 +89,6 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
||||
})
|
||||
: undefined
|
||||
},
|
||||
[ModuleOutputKeyEnum.moduleDispatchBills]: [
|
||||
{
|
||||
moduleName: plugin.name,
|
||||
totalPoints: moduleDispatchBills.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
|
||||
model: plugin.name,
|
||||
charsLength: 0
|
||||
}
|
||||
],
|
||||
...(output ? output.pluginOutput : {})
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ export const dispatchPluginOutput = (props: PluginOutputProps): PluginOutputResp
|
||||
|
||||
return {
|
||||
responseData: {
|
||||
totalPoints: 0,
|
||||
price: 0,
|
||||
pluginOutput: params
|
||||
}
|
||||
};
|
||||
|
||||
64
projects/app/src/service/moduleDispatch/tools/cfr.ts
Normal file
64
projects/app/src/service/moduleDispatch/tools/cfr.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { ChatItemType, moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { ModelTypeEnum, getLLMModel } from '@/service/core/ai/model';
|
||||
import { formatModelPrice2Store } from '@/service/support/wallet/bill/utils';
|
||||
import { queryCfr } from '@fastgpt/service/core/ai/functions/cfr';
|
||||
import { getHistories } from '../utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.aiModel]: string;
|
||||
[ModuleInputKeyEnum.aiSystemPrompt]?: string;
|
||||
[ModuleInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[ModuleInputKeyEnum.userChatInput]: string;
|
||||
}>;
|
||||
type Response = {
|
||||
[ModuleOutputKeyEnum.text]: string;
|
||||
[ModuleOutputKeyEnum.responseData]?: moduleDispatchResType;
|
||||
};
|
||||
|
||||
export const dispatchCFR = async ({
|
||||
histories,
|
||||
params: { model, systemPrompt, history, userChatInput }
|
||||
}: Props): Promise<Response> => {
|
||||
if (!userChatInput) {
|
||||
return Promise.reject('Question is empty');
|
||||
}
|
||||
|
||||
// none
|
||||
// first chat and no system prompt
|
||||
if (systemPrompt === 'none' || (histories.length === 0 && !systemPrompt)) {
|
||||
return {
|
||||
[ModuleOutputKeyEnum.text]: userChatInput
|
||||
};
|
||||
}
|
||||
|
||||
const cfrModel = getLLMModel(model);
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { cfrQuery, inputTokens, outputTokens } = await queryCfr({
|
||||
chatBg: systemPrompt,
|
||||
query: userChatInput,
|
||||
histories: chatHistories,
|
||||
model: cfrModel.model
|
||||
});
|
||||
|
||||
const { total, modelName } = formatModelPrice2Store({
|
||||
model: cfrModel.model,
|
||||
inputLen: inputTokens,
|
||||
outputLen: outputTokens,
|
||||
type: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
price: total,
|
||||
model: modelName,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
query: userChatInput,
|
||||
textOutput: cfrQuery
|
||||
},
|
||||
[ModuleOutputKeyEnum.text]: cfrQuery
|
||||
};
|
||||
};
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type {
|
||||
ModuleDispatchProps,
|
||||
ModuleDispatchResponse
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import {
|
||||
DYNAMIC_INPUT_KEY,
|
||||
ModuleInputKeyEnum,
|
||||
@@ -19,10 +16,11 @@ type HttpRequestProps = ModuleDispatchProps<{
|
||||
[ModuleInputKeyEnum.httpHeaders]: string;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
type HttpResponse = ModuleDispatchResponse<{
|
||||
type HttpResponse = {
|
||||
[ModuleOutputKeyEnum.failed]?: boolean;
|
||||
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
};
|
||||
|
||||
const flatDynamicParams = (params: Record<string, any>) => {
|
||||
const dynamicParams = params[DYNAMIC_INPUT_KEY];
|
||||
@@ -40,7 +38,7 @@ export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<Http
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
variables,
|
||||
module: { outputs },
|
||||
outputs,
|
||||
params: {
|
||||
system_httpMethod: httpMethod = 'POST',
|
||||
system_httpReqUrl: httpReqUrl,
|
||||
@@ -99,8 +97,8 @@ export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<Http
|
||||
}
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
totalPoints: 0,
|
||||
responseData: {
|
||||
price: 0,
|
||||
body: formatBody,
|
||||
httpResult: response
|
||||
},
|
||||
@@ -111,8 +109,8 @@ export const dispatchHttpRequest = async (props: HttpRequestProps): Promise<Http
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.failed]: true,
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
totalPoints: 0,
|
||||
responseData: {
|
||||
price: 0,
|
||||
body: formatBody,
|
||||
httpResult: { error }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type {
|
||||
ModuleDispatchProps,
|
||||
ModuleDispatchResponse
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type { moduleDispatchResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/module/type.d';
|
||||
import {
|
||||
DYNAMIC_INPUT_KEY,
|
||||
ModuleInputKeyEnum,
|
||||
@@ -26,10 +24,11 @@ type HttpRequestProps = ModuleDispatchProps<{
|
||||
[DYNAMIC_INPUT_KEY]: Record<string, any>;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
type HttpResponse = ModuleDispatchResponse<{
|
||||
type HttpResponse = {
|
||||
[ModuleOutputKeyEnum.failed]?: boolean;
|
||||
[ModuleOutputKeyEnum.responseData]: moduleDispatchResType;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
};
|
||||
|
||||
const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
|
||||
|
||||
@@ -39,7 +38,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
variables,
|
||||
module: { outputs },
|
||||
outputs,
|
||||
histories,
|
||||
params: {
|
||||
system_httpMethod: httpMethod = 'POST',
|
||||
@@ -120,8 +119,8 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
}
|
||||
|
||||
return {
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
totalPoints: 0,
|
||||
responseData: {
|
||||
price: 0,
|
||||
params: Object.keys(params).length > 0 ? params : undefined,
|
||||
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
|
||||
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
||||
@@ -132,8 +131,8 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
} catch (error) {
|
||||
return {
|
||||
[ModuleOutputKeyEnum.failed]: true,
|
||||
[ModuleOutputKeyEnum.responseData]: {
|
||||
totalPoints: 0,
|
||||
responseData: {
|
||||
price: 0,
|
||||
params: Object.keys(params).length > 0 ? params : undefined,
|
||||
body: Object.keys(requestBody).length > 0 ? requestBody : undefined,
|
||||
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user