feat: support sub route config (#3071)

* feat: support sub route config

* dockerfile

* fix upload

* delete unused code
This commit is contained in:
heheer
2024-11-07 13:53:23 +08:00
committed by archer
parent 45aa2e7374
commit 34fbd5a223
37 changed files with 100 additions and 168 deletions

View File

@@ -35,6 +35,8 @@ SANDBOX_URL=http://localhost:3001
PRO_URL=
# 页面的地址,用于自动补全相对路径资源的 domain
FE_DOMAIN=http://localhost:3000
# 二级路由
# NEXT_PUBLIC_BASE_URL=/fastai
# 日志等级: debug, info, warn, error
LOG_LEVEL=debug

View File

@@ -26,6 +26,7 @@ FROM node:20.14.0-alpine AS builder
WORKDIR /app
ARG proxy
ARG base_url
# copy common node_modules and one project node_modules
COPY package.json pnpm-workspace.yaml .npmrc tsconfig.json ./
@@ -39,6 +40,7 @@ RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /
RUN apk add --no-cache libc6-compat && npm install -g pnpm@9.4.0
ENV NODE_OPTIONS="--max-old-space-size=4096"
ENV NEXT_PUBLIC_BASE_URL=$base_url
RUN pnpm --filter=app build
# --------- runner -----------
@@ -46,6 +48,7 @@ FROM node:20.14.0-alpine AS runner
WORKDIR /app
ARG proxy
ARG base_url
# create user and use it
RUN addgroup --system --gid 1001 nodejs
@@ -81,6 +84,7 @@ RUN chown -R nextjs:nodejs /app/data
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV NEXT_PUBLIC_BASE_URL=$base_url
EXPOSE 3000

View File

@@ -6,6 +6,7 @@ const isDev = process.env.NODE_ENV === 'development';
/** @type {import('next').NextConfig} */
const nextConfig = {
basePath: process.env.NEXT_PUBLIC_BASE_URL,
i18n,
output: 'standalone',
reactStrictMode: isDev ? false : true,

View File

@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { Image, Skeleton, ImageProps } from '@chakra-ui/react';
import { Skeleton, ImageProps } from '@chakra-ui/react';
import CustomImage from '@fastgpt/web/components/common/Image/MyImage';
export const MyImage = (props: ImageProps) => {
const [isLoading, setIsLoading] = useState(true);
@@ -13,7 +14,7 @@ export const MyImage = (props: ImageProps) => {
justifyContent={'center'}
my={1}
>
<Image
<CustomImage
display={'inline-block'}
borderRadius={'md'}
alt={''}

View File

@@ -13,6 +13,7 @@ import MySelect from '@fastgpt/web/components/common/MySelect';
import { defaultTTSConfig } from '@fastgpt/global/core/app/constants';
import ChatFunctionTip from './Tip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
const TTSSelect = ({
value = defaultTTSConfig,
@@ -133,7 +134,7 @@ const TTSSelect = ({
<Flex mt={10} justifyContent={'end'}>
{audioPlaying ? (
<Flex>
<Image src="/icon/speaking.gif" w={'24px'} alt={''} />
<MyImage src="/icon/speaking.gif" w={'24px'} alt={''} />
<Button
ml={2}
variant={'grayBase'}

View File

@@ -1,5 +1,5 @@
import { useI18n } from '@/web/context/I18n';
import { Box, Flex, Image } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { useTranslation } from 'next-i18next';
import React, { useRef } from 'react';
@@ -77,7 +77,7 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
label={
<Box pt={2}>
<Flex alignItems={'flex-start'}>
<Image src={data.icon} w={'36px'} alt={''} />
<MyImage src={data.icon} w={'36px'} alt={''} />
<Box ml={3}>
<Box fontWeight="bold">{data.title}</Box>
<Box fontSize={'xs'} color={'myGray.500'}>
@@ -85,7 +85,7 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
</Box>
</Box>
</Flex>
<Image src={data.imgUrl} w={'100%'} minH={['auto', '250px']} mt={2} alt={''} />
<MyImage src={data.imgUrl} w={'100%'} minH={['auto', '250px']} mt={2} alt={''} />
</Box>
}
/>

View File

@@ -1,5 +1,5 @@
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { Flex, FlexProps, Image, css, useTheme } from '@chakra-ui/react';
import { Flex, FlexProps, css, useTheme } from '@chakra-ui/react';
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import React, { useMemo } from 'react';
@@ -9,6 +9,7 @@ import { formatChatValue2InputType } from '../utils';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { ChatBoxContext } from '../Provider';
import { useContextSelector } from 'use-context-selector';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
export type ChatControllerProps = {
isLastChild: boolean;
@@ -124,7 +125,7 @@ const ChatController = ({
onClick={cancelAudio}
/>
</MyTooltip>
<Image
<MyImage
src="/icon/speaking.gif"
w={'23px'}
alt={''}

View File

@@ -6,6 +6,7 @@ import { useTranslation } from 'next-i18next';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import QuoteItem from '@/components/core/dataset/QuoteItem';
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
const QuoteModal = ({
rawSearch = [],
@@ -42,13 +43,13 @@ const QuoteModal = ({
h={['90vh', '80vh']}
isCentered
minW={['90vw', '600px']}
iconSrc={!!metadata ? undefined : '/imgs/modal/quote.svg'}
iconSrc={!!metadata ? undefined : getWebReqUrl('/imgs/modal/quote.svg')}
title={
<Box>
{metadata ? (
<RawSourceBox {...metadata} canView={showDetail} />
) : (
<>{t('core.chat.Quote Amount', { amount: rawSearch.length })}</>
<>{t('common:core.chat.Quote Amount', { amount: rawSearch.length })}</>
)}
<Box fontSize={'xs'} color={'myGray.500'} fontWeight={'normal'}>
{t('common:core.chat.quote.Quote Tip')}

View File

@@ -6,6 +6,7 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
const RenderFilePreview = ({
fileList,
@@ -70,7 +71,7 @@ const RenderFilePreview = ({
/>
)}
{isImage && (
<Image
<MyImage
alt={'img'}
src={item.icon}
w={'full'}

View File

@@ -1,5 +1,6 @@
import { getCaptchaPic } from '@/web/support/user/api';
import { Button, Input, Image, ModalBody, ModalFooter, Skeleton } from '@chakra-ui/react';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
@@ -42,7 +43,7 @@ const SendCodeAuthModal = ({
justifyContent={'center'}
my={1}
>
<Image
<MyImage
borderRadius={'md'}
w={'100%'}
h={'200px'}

View File

@@ -13,6 +13,7 @@ import '@/web/styles/reset.scss';
import NextHead from '@/components/common/NextHead';
import { ReactElement, useEffect } from 'react';
import { NextPage } from 'next';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
type NextPageWithLayout = NextPage & {
setLayout?: (page: ReactElement) => JSX.Element;
@@ -49,7 +50,7 @@ function App({ Component, pageProps }: AppPropsWithLayout) {
process.env.SYSTEM_DESCRIPTION ||
`${title}${t('app:intro')}`
}
icon={feConfigs?.favicon || process.env.SYSTEM_FAVICON}
icon={getWebReqUrl(feConfigs?.favicon || process.env.SYSTEM_FAVICON)}
/>
{scripts?.map((item, i) => <Script key={i} strategy="lazyOnload" {...item}></Script>)}

View File

@@ -9,7 +9,6 @@ import {
Link,
Progress,
Grid,
Image,
BoxProps
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
@@ -45,6 +44,7 @@ import StandardPlanContentList from '@/components/support/wallet/StandardPlanCon
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
@@ -653,7 +653,7 @@ const Other = ({ onOpenContact }: { onOpenContact: () => void }) => {
onClick={onOpenLaf}
fontSize={'sm'}
>
<Image src="/imgs/workflow/laf.png" w={'18px'} alt="laf" />
<MyImage src="/imgs/workflow/laf.png" w={'18px'} alt="laf" />
<Box ml={2} flex={1}>
{'laf' + t('common:navbar.Account')}
</Box>

View File

@@ -13,6 +13,7 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import Script from 'next/script';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
const Promotion = dynamic(() => import('./components/Promotion'));
const UsageTable = dynamic(() => import('./components/UsageTable'));
@@ -128,7 +129,7 @@ const Account = ({ currentTab }: { currentTab: TabEnum }) => {
return (
<>
<Script src="/js/qrcode.min.js" strategy="lazyOnload"></Script>
<Script src={getWebReqUrl('/js/qrcode.min.js')} strategy="lazyOnload"></Script>
<PageContainer>
<Flex flexDirection={['column', 'row']} h={'100%'} pt={[4, 0]}>
{isPc ? (

View File

@@ -2,7 +2,7 @@ import { OutLinkSchema } from '@fastgpt/global/support/outLink/type';
import React, { useCallback, useState } from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { Box, Flex, FlexProps, Grid, Image, ModalBody, Switch, useTheme } from '@chakra-ui/react';
import { Box, Flex, FlexProps, Grid, ModalBody, Switch, useTheme } from '@chakra-ui/react';
import MyRadio from '@/components/common/MyRadio';
import { useForm } from 'react-hook-form';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -10,6 +10,7 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { fileToBase64 } from '@/web/common/file/utils';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
enum UsingWayEnum {
link = 'link',
@@ -29,15 +30,15 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
const VariableTypeList = [
{
title: <Image src={'/imgs/outlink/link.svg'} alt={''} />,
title: <MyImage src={'/imgs/outlink/link.svg'} alt={''} />,
value: UsingWayEnum.link
},
{
title: <Image src={'/imgs/outlink/iframe.svg'} alt={''} />,
title: <MyImage src={'/imgs/outlink/iframe.svg'} alt={''} />,
value: UsingWayEnum.iframe
},
{
title: <Image src={'/imgs/outlink/script.svg'} alt={''} />,
title: <MyImage src={'/imgs/outlink/script.svg'} alt={''} />,
value: UsingWayEnum.script
}
];
@@ -162,7 +163,7 @@ console.log("Chat box loaded")
</Flex>
<Flex {...gridItemStyle}>
<Box flex={1}>{t('common:core.app.outLink.Script Open Icon')}</Box>
<Image
<MyImage
src={getValues('scriptOpenIcon')}
alt={''}
w={'20px'}
@@ -173,7 +174,7 @@ console.log("Chat box loaded")
</Flex>
<Flex {...gridItemStyle}>
<Box flex={1}>{t('common:core.app.outLink.Script Close Icon')}</Box>
<Image
<MyImage
src={getValues('scriptCloseIcon')}
alt={''}
w={'20px'}

View File

@@ -3,6 +3,7 @@ import { Box, Image, Flex, ModalBody } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
export type ShowShareLinkModalProps = {
shareLink: string;
@@ -40,7 +41,7 @@ function ShowShareLinkModal({ shareLink, onClose, img }: ShowShareLinkModalProps
</Box>
</Box>
<Box mt="4" borderRadius="0.5rem" border="1px" borderStyle="solid" borderColor="myGray.200">
<Image src={img} borderRadius="0.5rem" alt="" />
<MyImage src={img} borderRadius="0.5rem" alt="" />
</Box>
</ModalBody>
</MyModal>

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useMemo } from 'react';
import { Box, Button, Card, Flex, FlexProps, Image } from '@chakra-ui/react';
import { Box, Button, Card, Flex, FlexProps } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
@@ -15,7 +15,7 @@ import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/Connect
import { useDebug } from '../../hooks/useDebug';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { getPreviewPluginNode } from '@/web/core/app/api/plugin';
import { storeNode2FlowNode, getLatestNodeTemplate } from '@/web/core/workflow/utils';
import { storeNode2FlowNode } from '@/web/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
@@ -28,6 +28,7 @@ import { WholeResponseContent } from '@/components/core/chat/components/WholeRes
import { getDocPath } from '@/web/common/system/doc';
import { WorkflowActionContext } from '../../../context/workflowInitContext';
import { WorkflowEventContext } from '../../../context/workflowEventContext';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
@@ -244,7 +245,7 @@ const NodeCard = (props: Props) => {
{!!nodeTemplate?.diagram && !hasNewVersion && (
<MyTooltip
label={
<Image
<MyImage
src={nodeTemplate?.diagram}
w={'100%'}
minH={['auto', '200px']}

View File

@@ -1,7 +1,7 @@
import React, { Dispatch } from 'react';
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
import type { ResLogin } from '@/global/support/api/userRes';
import { Box, Center, Image } from '@chakra-ui/react';
import { Box, Center } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { getWXLoginQR, getWXLoginResult } from '@/web/support/user/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -9,6 +9,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import FormLayout from './components/FormLayout';
import { useTranslation } from 'next-i18next';
import Loading from '@fastgpt/web/components/common/MyLoading';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
interface Props {
loginSuccess: (e: ResLogin) => void;
@@ -44,7 +45,7 @@ const WechatForm = ({ setPageType, loginSuccess }: Props) => {
</Box>
<Box p={5} display={'flex'} w={'full'} justifyContent={'center'}>
{wechatInfo?.codeUrl ? (
<Image w="200px" src={wechatInfo?.codeUrl} alt="qrcode"></Image>
<MyImage w="200px" src={wechatInfo?.codeUrl} alt="qrcode"></MyImage>
) : (
<Center w={200} h={200} position={'relative'}>
<Loading fixed={false} />

View File

@@ -1,6 +1,6 @@
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { AbsoluteCenter, Box, Button, Flex, Image } from '@chakra-ui/react';
import { AbsoluteCenter, Box, Button, Flex } from '@chakra-ui/react';
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
import { OAuthEnum } from '@fastgpt/global/support/user/constant';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -10,6 +10,7 @@ import { Dispatch, useRef } from 'react';
import { useTranslation } from 'next-i18next';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
interface Props {
@@ -87,7 +88,7 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
alignItems={'center'}
justifyContent={'center'}
>
<Image src={LOGO_ICON} w={['22.5px', '36px']} alt={'icon'} />
<MyImage src={LOGO_ICON} w={['22.5px', '36px']} alt={'icon'} />
</Flex>
<Box ml={[3, 5]} fontSize={['lg', 'xl']} fontWeight={'bold'} color={'myGray.900'}>
{feConfigs?.systemTitle}
@@ -138,7 +139,7 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
w={'100%'}
h={'40px'}
borderRadius={'sm'}
leftIcon={<Image alt="" src={feConfigs.sso.icon as any} w="20px" />}
leftIcon={<MyImage alt="" src={feConfigs.sso.icon as any} w="20px" />}
onClick={() => {
feConfigs.sso?.url && router.replace(feConfigs.sso?.url, '_self');
}}

View File

@@ -28,6 +28,7 @@ import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { GET } from '@/web/common/api/request';
import { getDocPath } from '@/web/common/system/doc';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
@@ -141,7 +142,7 @@ const Login = ({ ChineseRedirectUrl }: { ChineseRedirectUrl: string }) => {
<Flex
alignItems={'center'}
justifyContent={'center'}
bg={`url('/icon/login-bg.svg') no-repeat`}
bg={`url(${getWebReqUrl('/icon/login-bg.svg')}) no-repeat`}
backgroundSize={'cover'}
userSelect={'none'}
h={'100%'}

View File

@@ -11,6 +11,7 @@ import {
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { useSystemStore } from '../system/useSystemStore';
import { formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
type StreamFetchProps = {
url?: string;
@@ -136,7 +137,7 @@ export const streamFetch = ({
};
// send request
await fetchEventSource(url, {
await fetchEventSource(getWebReqUrl(url), {
...requestData,
async onopen(res) {
clearTimeout(timeoutId);

View File

@@ -8,6 +8,7 @@ import { clearToken } from '@/web/support/user/auth';
import { TOKEN_ERROR_CODE } from '@fastgpt/global/common/error/errorCode';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { useSystemStore } from '../system/useSystemStore';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
interface ConfigType {
headers?: { [key: string]: string };
@@ -114,7 +115,7 @@ function responseError(err: any) {
!(window.location.pathname === '/chat/share' || window.location.pathname === '/chat/team')
) {
window.location.replace(
`/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`
getWebReqUrl(`/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}`)
);
}
@@ -160,7 +161,7 @@ function request(
return instance
.request({
baseURL: '/api',
baseURL: getWebReqUrl('/api'),
url,
method,
data: ['POST', 'PUT'].includes(method) ? data : null,

View File

@@ -1,7 +1,8 @@
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
import { useQuery } from '@tanstack/react-query';
export const getMd = async (url: string) => {
const response = await fetch(`/docs/${url}`);
const response = await fetch(getWebReqUrl(`/docs/${url}`));
const textContent = await response.text();
return textContent;
};

View File

@@ -1,3 +1,4 @@
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
import { useSystemStore } from './useSystemStore';
export const getDocPath = (path: string) => {
const feConfigs = useSystemStore.getState().feConfigs;
@@ -5,5 +6,6 @@ export const getDocPath = (path: string) => {
if (!feConfigs?.docUrl) return '';
if (!path.startsWith('/')) return path;
if (feConfigs.docUrl.endsWith('/')) return feConfigs.docUrl.slice(0, -1);
return feConfigs.docUrl + path;
return getWebReqUrl(feConfigs.docUrl + path);
};

View File

@@ -8,6 +8,7 @@ import { TTSTypeEnum } from '@/web/core/app/constants';
import { useTranslation } from 'next-i18next';
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
import { useMount } from 'ahooks';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
const contentType = 'audio/mpeg';
const splitMarker = 'SPLIT_MARKER';
@@ -45,7 +46,7 @@ export const useAudioPlay = (props?: OutLinkChatAuthProps & { ttsConfig?: AppTTS
setAudioLoading(true);
audioController.current = new AbortController();
const response = await fetch('/api/core/chat/item/getSpeech', {
const response = await fetch(getWebReqUrl('/api/core/chat/item/getSpeech'), {
method: 'POST',
headers: {
'Content-Type': 'application/json'