From b035f3f8849b677e73cf43ccfd9e37f6d52bab23 Mon Sep 17 00:00:00 2001
From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com>
Date: Fri, 18 Jul 2025 11:43:37 +0800
Subject: [PATCH] feat: convert components to dynamic imports for improved
performance (#22614)
---
.../[appId]/workflow/page.tsx | 2 -
web/app/(commonLayout)/apps/assets/add.svg | 3 -
.../(commonLayout)/apps/assets/chat-solid.svg | 4 -
web/app/(commonLayout)/apps/assets/chat.svg | 3 -
.../apps/assets/completion-solid.svg | 4 -
.../(commonLayout)/apps/assets/completion.svg | 3 -
.../(commonLayout)/apps/assets/discord.svg | 3 -
web/app/(commonLayout)/apps/assets/github.svg | 17 --
.../(commonLayout)/apps/assets/link-gray.svg | 3 -
web/app/(commonLayout)/apps/assets/link.svg | 3 -
.../apps/assets/right-arrow.svg | 3 -
web/app/(commonLayout)/apps/layout.tsx | 12 -
web/app/(commonLayout)/apps/page.tsx | 28 +--
web/app/(commonLayout)/list.module.css | 217 ------------------
web/app/components/app-sidebar/app-info.tsx | 26 ++-
.../components/app/app-publisher/index.tsx | 6 +-
.../app/app-publisher/suggested-action.tsx | 4 +-
.../components/app/type-selector/index.tsx | 76 +++---
.../apps/app-card.tsx} | 30 ++-
web/app/components/apps/empty.tsx | 35 +++
web/app/components/apps/footer.tsx | 46 ++++
.../apps/hooks/use-apps-query-state.ts | 0
.../apps/hooks/use-dsl-drag-drop.ts | 2 +-
web/app/components/apps/index.tsx | 26 +++
.../Apps.tsx => components/apps/list.tsx} | 39 ++--
.../apps/new-app-card.tsx} | 124 +++++-----
web/app/components/base/app-icon/index.tsx | 3 +-
.../components/workflow-children.tsx | 14 +-
.../components/workflow-panel.tsx | 34 ++-
web/app/components/workflow-app/index.tsx | 2 +
.../workflow/header/header-in-normal.tsx | 3 +-
web/app/components/workflow/header/index.tsx | 11 +-
web/app/components/workflow/index.tsx | 6 +-
web/context/modal-context.tsx | 50 +++-
web/next.config.js | 5 +-
web/package.json | 4 +-
web/pnpm-lock.yaml | 114 +++++++++
37 files changed, 493 insertions(+), 472 deletions(-)
delete mode 100644 web/app/(commonLayout)/apps/assets/add.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/chat-solid.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/chat.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/completion-solid.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/completion.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/discord.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/github.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/link-gray.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/link.svg
delete mode 100644 web/app/(commonLayout)/apps/assets/right-arrow.svg
delete mode 100644 web/app/(commonLayout)/apps/layout.tsx
delete mode 100644 web/app/(commonLayout)/list.module.css
rename web/app/{(commonLayout)/apps/AppCard.tsx => components/apps/app-card.tsx} (95%)
create mode 100644 web/app/components/apps/empty.tsx
create mode 100644 web/app/components/apps/footer.tsx
rename web/app/{(commonLayout) => components}/apps/hooks/use-apps-query-state.ts (100%)
rename web/app/{(commonLayout) => components}/apps/hooks/use-dsl-drag-drop.ts (97%)
create mode 100644 web/app/components/apps/index.tsx
rename web/app/{(commonLayout)/apps/Apps.tsx => components/apps/list.tsx} (90%)
rename web/app/{(commonLayout)/apps/NewAppCard.tsx => components/apps/new-app-card.tsx} (56%)
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx
index d5df70f00..15da0bbed 100644
--- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx
+++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/workflow/page.tsx
@@ -1,5 +1,3 @@
-'use client'
-
import WorkflowApp from '@/app/components/workflow-app'
const Page = () => {
diff --git a/web/app/(commonLayout)/apps/assets/add.svg b/web/app/(commonLayout)/apps/assets/add.svg
deleted file mode 100644
index 9958e855a..000000000
--- a/web/app/(commonLayout)/apps/assets/add.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/chat-solid.svg b/web/app/(commonLayout)/apps/assets/chat-solid.svg
deleted file mode 100644
index a793e982c..000000000
--- a/web/app/(commonLayout)/apps/assets/chat-solid.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/chat.svg b/web/app/(commonLayout)/apps/assets/chat.svg
deleted file mode 100644
index 0971349a5..000000000
--- a/web/app/(commonLayout)/apps/assets/chat.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/completion-solid.svg b/web/app/(commonLayout)/apps/assets/completion-solid.svg
deleted file mode 100644
index a9dc7e3dc..000000000
--- a/web/app/(commonLayout)/apps/assets/completion-solid.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/completion.svg b/web/app/(commonLayout)/apps/assets/completion.svg
deleted file mode 100644
index 34af4417f..000000000
--- a/web/app/(commonLayout)/apps/assets/completion.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/discord.svg b/web/app/(commonLayout)/apps/assets/discord.svg
deleted file mode 100644
index 9f22a1ab5..000000000
--- a/web/app/(commonLayout)/apps/assets/discord.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/github.svg b/web/app/(commonLayout)/apps/assets/github.svg
deleted file mode 100644
index f03798b5e..000000000
--- a/web/app/(commonLayout)/apps/assets/github.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/link-gray.svg b/web/app/(commonLayout)/apps/assets/link-gray.svg
deleted file mode 100644
index a293cfcf5..000000000
--- a/web/app/(commonLayout)/apps/assets/link-gray.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/link.svg b/web/app/(commonLayout)/apps/assets/link.svg
deleted file mode 100644
index 2926c28b1..000000000
--- a/web/app/(commonLayout)/apps/assets/link.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/assets/right-arrow.svg b/web/app/(commonLayout)/apps/assets/right-arrow.svg
deleted file mode 100644
index a2c1cedf9..000000000
--- a/web/app/(commonLayout)/apps/assets/right-arrow.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/web/app/(commonLayout)/apps/layout.tsx b/web/app/(commonLayout)/apps/layout.tsx
deleted file mode 100644
index 10d04a418..000000000
--- a/web/app/(commonLayout)/apps/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-'use client'
-
-import useDocumentTitle from '@/hooks/use-document-title'
-import { useTranslation } from 'react-i18next'
-
-export default function DatasetsLayout({ children }: { children: React.ReactNode }) {
- const { t } = useTranslation()
- useDocumentTitle(t('common.menus.apps'))
- return (<>
- {children}
- >)
-}
diff --git a/web/app/(commonLayout)/apps/page.tsx b/web/app/(commonLayout)/apps/page.tsx
index 3f617d41c..25b6d55d1 100644
--- a/web/app/(commonLayout)/apps/page.tsx
+++ b/web/app/(commonLayout)/apps/page.tsx
@@ -1,32 +1,8 @@
-'use client'
-import { useTranslation } from 'react-i18next'
-import { RiDiscordFill, RiGithubFill } from '@remixicon/react'
-import Link from 'next/link'
-import style from '../list.module.css'
-import Apps from './Apps'
-import { useEducationInit } from '@/app/education-apply/hooks'
-import { useGlobalPublicStore } from '@/context/global-public-context'
+import Apps from '@/app/components/apps'
const AppList = () => {
- const { t } = useTranslation()
- useEducationInit()
- const { systemFeatures } = useGlobalPublicStore()
return (
-
-
- {!systemFeatures.branding.enabled &&
}
-
+
)
}
diff --git a/web/app/(commonLayout)/list.module.css b/web/app/(commonLayout)/list.module.css
deleted file mode 100644
index c4d3aec29..000000000
--- a/web/app/(commonLayout)/list.module.css
+++ /dev/null
@@ -1,217 +0,0 @@
-.listItem {
- @apply col-span-1 bg-white border-2 border-solid border-transparent rounded-xl shadow-xs min-h-[160px] flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg;
-}
-
-.listItem.newItemCard {
- @apply outline outline-1 outline-gray-200 -outline-offset-1 hover:shadow-sm hover:bg-white;
- background-color: rgba(229, 231, 235, 0.5);
-}
-
-.listItem.selectable {
- @apply relative bg-gray-50 outline outline-1 outline-gray-200 -outline-offset-1 shadow-none hover:bg-none hover:shadow-none hover:outline-primary-200 transition-colors;
-}
-
-.listItem.selectable * {
- @apply relative;
-}
-
-.listItem.selectable::before {
- content: "";
- @apply absolute top-0 left-0 block w-full h-full rounded-lg pointer-events-none opacity-0 transition-opacity duration-200 ease-in-out hover:opacity-100;
- background: linear-gradient(0deg,
- rgba(235, 245, 255, 0.5),
- rgba(235, 245, 255, 0.5)),
- #ffffff;
-}
-
-.listItem.selectable:hover::before {
- @apply opacity-100;
-}
-
-.listItem.selected {
- @apply border-primary-600 hover:border-primary-600 border-2;
-}
-
-.listItem.selected::before {
- @apply opacity-100;
-}
-
-.appIcon {
- @apply flex items-center justify-center w-8 h-8 bg-pink-100 rounded-lg grow-0 shrink-0;
-}
-
-.appIcon.medium {
- @apply w-9 h-9;
-}
-
-.appIcon.large {
- @apply w-10 h-10;
-}
-
-.newItemIcon {
- @apply flex items-center justify-center w-8 h-8 transition-colors duration-200 ease-in-out border border-gray-200 rounded-lg hover:bg-white grow-0 shrink-0;
-}
-
-.listItem:hover .newItemIcon {
- @apply bg-gray-50 border-primary-100;
-}
-
-.newItemCard .newItemIcon {
- @apply bg-gray-100;
-}
-
-.newItemCard:hover .newItemIcon {
- @apply bg-white;
-}
-
-.selectable .newItemIcon {
- @apply bg-gray-50;
-}
-
-.selectable:hover .newItemIcon {
- @apply bg-primary-50;
-}
-
-.newItemIconImage {
- @apply grow-0 shrink-0 block w-4 h-4 bg-center bg-contain transition-colors duration-200 ease-in-out;
- color: #1f2a37;
-}
-
-.listItem:hover .newIconImage {
- @apply text-primary-600;
-}
-
-.newItemIconAdd {
- background-image: url("./apps/assets/add.svg");
-}
-
-/* .newItemIconChat {
- background-image: url("~@/app/components/base/icons/assets/public/header-nav/studio/Robot.svg");
-}
-
-.selected .newItemIconChat {
- background-image: url("~@/app/components/base/icons/assets/public/header-nav/studio/Robot-Active.svg");
-} */
-
-.newItemIconComplete {
- background-image: url("./apps/assets/completion.svg");
-}
-
-.listItemTitle {
- @apply flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0;
-}
-
-.listItemHeading {
- @apply relative h-8 text-sm font-medium leading-8 grow;
-}
-
-.listItemHeadingContent {
- @apply absolute top-0 left-0 w-full h-full overflow-hidden text-ellipsis whitespace-nowrap;
-}
-
-.actionIconWrapper {
- @apply hidden h-8 w-8 p-2 rounded-md border-none hover:bg-gray-100 !important;
-}
-
-.listItem:hover .actionIconWrapper {
- @apply !inline-flex;
-}
-
-.deleteDatasetIcon {
- @apply hidden grow-0 shrink-0 basis-8 w-8 h-8 rounded-lg transition-colors duration-200 ease-in-out bg-white border border-gray-200 hover:bg-gray-100 bg-center bg-no-repeat;
- background-size: 16px;
- background-image: url('~@/assets/delete.svg');
-}
-
-.listItem:hover .deleteDatasetIcon {
- @apply block;
-}
-
-.listItemDescription {
- @apply mb-3 px-[14px] h-9 text-xs leading-normal text-gray-500 line-clamp-2;
-}
-
-.listItemDescription.noClip {
- @apply line-clamp-none;
-}
-
-.listItemFooter {
- @apply flex items-center flex-wrap min-h-[42px] px-[14px] pt-2 pb-[10px];
-}
-
-.listItemFooter.datasetCardFooter {
- @apply flex items-center gap-4 text-xs text-gray-500;
-}
-
-.listItemStats {
- @apply flex items-center gap-1;
-}
-
-.listItemFooterIcon {
- @apply block w-3 h-3 bg-center bg-contain;
-}
-
-.solidChatIcon {
- background-image: url("./apps/assets/chat-solid.svg");
-}
-
-.solidCompletionIcon {
- background-image: url("./apps/assets/completion-solid.svg");
-}
-
-.newItemCardHeading {
- @apply transition-colors duration-200 ease-in-out;
-}
-
-.listItem:hover .newItemCardHeading {
- @apply text-primary-600;
-}
-
-.listItemLink {
- @apply inline-flex items-center gap-1 text-xs text-gray-400 transition-colors duration-200 ease-in-out;
-}
-
-.listItem:hover .listItemLink {
- @apply text-primary-600;
-}
-
-.linkIcon {
- @apply block w-[13px] h-[13px] bg-center bg-contain;
- background-image: url("./apps/assets/link.svg");
-}
-
-.linkIcon.grayLinkIcon {
- background-image: url("./apps/assets/link-gray.svg");
-}
-
-.listItem:hover .grayLinkIcon {
- background-image: url("./apps/assets/link.svg");
-}
-
-.rightIcon {
- @apply block w-[13px] h-[13px] bg-center bg-contain;
- background-image: url("./apps/assets/right-arrow.svg");
-}
-
-.socialMediaLink {
- @apply flex items-center justify-center w-8 h-8 cursor-pointer hover:opacity-80 transition-opacity duration-200 ease-in-out;
-}
-
-.socialMediaIcon {
- @apply block w-6 h-6 bg-center bg-contain;
-}
-
-/* #region new app dialog */
-.newItemCaption {
- @apply inline-flex items-center mb-2 text-sm font-medium;
-}
-
-/* #endregion new app dialog */
-
-.unavailable {
- @apply opacity-50;
-}
-
-.listItem:hover .unavailable {
- @apply opacity-100;
-}
diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx
index d5a04ec42..e85eaa2f5 100644
--- a/web/app/components/app-sidebar/app-info.tsx
+++ b/web/app/components/app-sidebar/app-info.tsx
@@ -12,23 +12,17 @@ import {
RiFileUploadLine,
} from '@remixicon/react'
import AppIcon from '../base/app-icon'
-import SwitchAppModal from '../app/switch-app-modal'
import cn from '@/utils/classnames'
-import Confirm from '@/app/components/base/confirm'
import { useStore as useAppStore } from '@/app/components/app/store'
import { ToastContext } from '@/app/components/base/toast'
import AppsContext, { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps'
-import DuplicateAppModal from '@/app/components/app/duplicate-modal'
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
-import CreateAppModal from '@/app/components/explore/create-app-modal'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { getRedirection } from '@/utils/app-redirection'
-import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal'
import type { EnvironmentVariable } from '@/app/components/workflow/types'
-import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
import { fetchWorkflowDraft } from '@/service/workflow'
import ContentDialog from '@/app/components/base/content-dialog'
import Button from '@/app/components/base/button'
@@ -36,6 +30,26 @@ import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overvie
import Divider from '../base/divider'
import type { Operation } from './app-operations'
import AppOperations from './app-operations'
+import dynamic from 'next/dynamic'
+
+const SwitchAppModal = dynamic(() => import('@/app/components/app/switch-app-modal'), {
+ ssr: false,
+})
+const CreateAppModal = dynamic(() => import('@/app/components/explore/create-app-modal'), {
+ ssr: false,
+})
+const DuplicateAppModal = dynamic(() => import('@/app/components/app/duplicate-modal'), {
+ ssr: false,
+})
+const Confirm = dynamic(() => import('@/app/components/base/confirm'), {
+ ssr: false,
+})
+const UpdateDSLModal = dynamic(() => import('@/app/components/workflow/update-dsl-modal'), {
+ ssr: false,
+})
+const DSLExportConfirmModal = dynamic(() => import('@/app/components/workflow/dsl-export-confirm-modal'), {
+ ssr: false,
+})
export type IAppInfoProps = {
expand: boolean
diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx
index 83a7ffd55..cb98aa495 100644
--- a/web/app/components/app/app-publisher/index.tsx
+++ b/web/app/components/app/app-publisher/index.tsx
@@ -6,6 +6,7 @@ import {
} from 'react'
import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
+import relativeTime from 'dayjs/plugin/relativeTime'
import {
RiArrowDownSLine,
RiArrowRightSLine,
@@ -48,6 +49,7 @@ import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/acces
import { AccessMode } from '@/models/access-control'
import { fetchAppDetail } from '@/service/apps'
import { useGlobalPublicStore } from '@/context/global-public-context'
+dayjs.extend(relativeTime)
export type AppPublisherProps = {
disabled?: boolean
@@ -116,6 +118,7 @@ const AppPublisher = ({
}
}, [appAccessSubjects, appDetail])
const language = useGetLanguage()
+
const formatTimeFromNow = useCallback((time: number) => {
return dayjs(time).locale(language === 'zh_Hans' ? 'zh-cn' : language.replace('_', '-')).fromNow()
}, [language])
@@ -180,8 +183,7 @@ const AppPublisher = ({
if (publishDisabled || published)
return
handlePublish()
- },
- { exactMatch: true, useCapture: true })
+ }, { exactMatch: true, useCapture: true })
return (
<>
diff --git a/web/app/components/app/app-publisher/suggested-action.tsx b/web/app/components/app/app-publisher/suggested-action.tsx
index 8d4ab3d39..2535de665 100644
--- a/web/app/components/app/app-publisher/suggested-action.tsx
+++ b/web/app/components/app/app-publisher/suggested-action.tsx
@@ -20,8 +20,8 @@ const SuggestedAction = ({ icon, link, disabled, children, className, onClick, .
target='_blank'
rel='noreferrer'
className={classNames(
- 'flex justify-start items-center gap-2 py-2 px-2.5 bg-background-section-burn rounded-lg text-text-secondary transition-colors [&:not(:first-child)]:mt-1',
- disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'text-text-secondary hover:bg-state-accent-hover hover:text-text-accent cursor-pointer',
+ 'flex items-center justify-start gap-2 rounded-lg bg-background-section-burn px-2.5 py-2 text-text-secondary transition-colors [&:not(:first-child)]:mt-1',
+ disabled ? 'cursor-not-allowed opacity-30 shadow-xs' : 'cursor-pointer text-text-secondary hover:bg-state-accent-hover hover:text-text-accent',
className,
)}
onClick={handleClick}
diff --git a/web/app/components/app/type-selector/index.tsx b/web/app/components/app/type-selector/index.tsx
index a57bac20d..99a76d7ac 100644
--- a/web/app/components/app/type-selector/index.tsx
+++ b/web/app/components/app/type-selector/index.tsx
@@ -65,6 +65,44 @@ const AppTypeSelector = ({ value, onChange }: AppSelectorProps) => {
export default AppTypeSelector
+type AppTypeIconProps = {
+ type: AppMode
+ style?: React.CSSProperties
+ className?: string
+ wrapperClassName?: string
+}
+
+export const AppTypeIcon = React.memo(({ type, className, wrapperClassName, style }: AppTypeIconProps) => {
+ const wrapperClassNames = cn('inline-flex h-5 w-5 items-center justify-center rounded-md border border-divider-regular', wrapperClassName)
+ const iconClassNames = cn('h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100', className)
+ if (type === 'chat') {
+ return
+
+
+ }
+ if (type === 'agent-chat') {
+ return
+
+
+ }
+ if (type === 'advanced-chat') {
+ return
+
+
+ }
+ if (type === 'workflow') {
+ return
+
+
+ }
+ if (type === 'completion') {
+ return
+
+
+ }
+ return null
+})
+
function AppTypeSelectTrigger({ values }: { values: AppSelectorProps['value'] }) {
const { t } = useTranslation()
if (!values || values.length === 0) {
@@ -108,44 +146,6 @@ function AppTypeSelectorItem({ checked, type, onClick }: AppTypeSelectorItemProp
}
-type AppTypeIconProps = {
- type: AppMode
- style?: React.CSSProperties
- className?: string
- wrapperClassName?: string
-}
-
-export function AppTypeIcon({ type, className, wrapperClassName, style }: AppTypeIconProps) {
- const wrapperClassNames = cn('inline-flex h-5 w-5 items-center justify-center rounded-md border border-divider-regular', wrapperClassName)
- const iconClassNames = cn('h-3.5 w-3.5 text-components-avatar-shape-fill-stop-100', className)
- if (type === 'chat') {
- return
-
-
- }
- if (type === 'agent-chat') {
- return
-
-
- }
- if (type === 'advanced-chat') {
- return
-
-
- }
- if (type === 'workflow') {
- return
-
-
- }
- if (type === 'completion') {
- return
-
-
- }
- return null
-}
-
type AppTypeLabelProps = {
type: AppMode
className?: string
diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/components/apps/app-card.tsx
similarity index 95%
rename from web/app/(commonLayout)/apps/AppCard.tsx
rename to web/app/components/apps/app-card.tsx
index b3a1dce5e..bfb7813bf 100644
--- a/web/app/(commonLayout)/apps/AppCard.tsx
+++ b/web/app/components/apps/app-card.tsx
@@ -1,16 +1,14 @@
'use client'
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { useContext, useContextSelector } from 'use-context-selector'
import { useRouter } from 'next/navigation'
-import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { RiBuildingLine, RiGlobalLine, RiLockLine, RiMoreFill, RiVerifiedBadgeLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import type { App } from '@/types/app'
-import Confirm from '@/app/components/base/confirm'
import Toast, { ToastContext } from '@/app/components/base/toast'
import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/apps'
-import DuplicateAppModal from '@/app/components/app/duplicate-modal'
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
import AppIcon from '@/app/components/base/app-icon'
import AppsContext, { useAppContext } from '@/context/app-context'
@@ -22,21 +20,37 @@ import { getRedirection } from '@/utils/app-redirection'
import { useProviderContext } from '@/context/provider-context'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
-import EditAppModal from '@/app/components/explore/create-app-modal'
-import SwitchAppModal from '@/app/components/app/switch-app-modal'
import type { Tag } from '@/app/components/base/tag-management/constant'
import TagSelector from '@/app/components/base/tag-management/selector'
import type { EnvironmentVariable } from '@/app/components/workflow/types'
-import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
import { fetchWorkflowDraft } from '@/service/workflow'
import { fetchInstalledAppList } from '@/service/explore'
import { AppTypeIcon } from '@/app/components/app/type-selector'
import Tooltip from '@/app/components/base/tooltip'
-import AccessControl from '@/app/components/app/app-access-control'
import { AccessMode } from '@/models/access-control'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { formatTime } from '@/utils/time'
import { useGetUserCanAccessApp } from '@/service/access-control'
+import dynamic from 'next/dynamic'
+
+const EditAppModal = dynamic(() => import('@/app/components/explore/create-app-modal'), {
+ ssr: false,
+})
+const DuplicateAppModal = dynamic(() => import('@/app/components/app/duplicate-modal'), {
+ ssr: false,
+})
+const SwitchAppModal = dynamic(() => import('@/app/components/app/switch-app-modal'), {
+ ssr: false,
+})
+const Confirm = dynamic(() => import('@/app/components/base/confirm'), {
+ ssr: false,
+})
+const DSLExportConfirmModal = dynamic(() => import('@/app/components/workflow/dsl-export-confirm-modal'), {
+ ssr: false,
+})
+const AccessControl = dynamic(() => import('@/app/components/app/app-access-control'), {
+ ssr: false,
+})
export type AppCardProps = {
app: App
@@ -483,4 +497,4 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
)
}
-export default AppCard
+export default React.memo(AppCard)
diff --git a/web/app/components/apps/empty.tsx b/web/app/components/apps/empty.tsx
new file mode 100644
index 000000000..e6b52294a
--- /dev/null
+++ b/web/app/components/apps/empty.tsx
@@ -0,0 +1,35 @@
+import React from 'react'
+import { useTranslation } from 'react-i18next'
+
+const DefaultCards = React.memo(() => {
+ const renderArray = Array.from({ length: 36 })
+ return (
+ <>
+ {
+ renderArray.map((_, index) => (
+
+ ))
+ }
+ >
+ )
+})
+
+const Empty = () => {
+ const { t } = useTranslation()
+
+ return (
+ <>
+
+
+
+ {t('app.newApp.noAppsFound')}
+
+
+ >
+ )
+}
+
+export default React.memo(Empty)
diff --git a/web/app/components/apps/footer.tsx b/web/app/components/apps/footer.tsx
new file mode 100644
index 000000000..7bee27234
--- /dev/null
+++ b/web/app/components/apps/footer.tsx
@@ -0,0 +1,46 @@
+import React from 'react'
+import Link from 'next/link'
+import { RiDiscordFill, RiGithubFill } from '@remixicon/react'
+import { useTranslation } from 'react-i18next'
+
+type CustomLinkProps = {
+ href: string
+ children: React.ReactNode
+}
+
+const CustomLink = React.memo(({
+ href,
+ children,
+}: CustomLinkProps) => {
+ return (
+
+ {children}
+
+ )
+})
+
+const Footer = () => {
+ const { t } = useTranslation()
+
+ return (
+
+ )
+}
+
+export default React.memo(Footer)
diff --git a/web/app/(commonLayout)/apps/hooks/use-apps-query-state.ts b/web/app/components/apps/hooks/use-apps-query-state.ts
similarity index 100%
rename from web/app/(commonLayout)/apps/hooks/use-apps-query-state.ts
rename to web/app/components/apps/hooks/use-apps-query-state.ts
diff --git a/web/app/(commonLayout)/apps/hooks/use-dsl-drag-drop.ts b/web/app/components/apps/hooks/use-dsl-drag-drop.ts
similarity index 97%
rename from web/app/(commonLayout)/apps/hooks/use-dsl-drag-drop.ts
rename to web/app/components/apps/hooks/use-dsl-drag-drop.ts
index 96942ec54..dda577306 100644
--- a/web/app/(commonLayout)/apps/hooks/use-dsl-drag-drop.ts
+++ b/web/app/components/apps/hooks/use-dsl-drag-drop.ts
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
type DSLDragDropHookProps = {
onDSLFileDropped: (file: File) => void
- containerRef: React.RefObject
+ containerRef: React.RefObject
enabled?: boolean
}
diff --git a/web/app/components/apps/index.tsx b/web/app/components/apps/index.tsx
new file mode 100644
index 000000000..be81a77dc
--- /dev/null
+++ b/web/app/components/apps/index.tsx
@@ -0,0 +1,26 @@
+'use client'
+import { useEducationInit } from '@/app/education-apply/hooks'
+import { useGlobalPublicStore } from '@/context/global-public-context'
+import List from './list'
+import Footer from './footer'
+import useDocumentTitle from '@/hooks/use-document-title'
+import { useTranslation } from 'react-i18next'
+
+const Apps = () => {
+ const { t } = useTranslation()
+ const { systemFeatures } = useGlobalPublicStore()
+
+ useDocumentTitle(t('common.menus.apps'))
+ useEducationInit()
+
+ return (
+
+
+ {!systemFeatures.branding.enabled && (
+
+ )}
+
+ )
+}
+
+export default Apps
diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/components/apps/list.tsx
similarity index 90%
rename from web/app/(commonLayout)/apps/Apps.tsx
rename to web/app/components/apps/list.tsx
index 2aa192fb0..359eaeabd 100644
--- a/web/app/(commonLayout)/apps/Apps.tsx
+++ b/web/app/components/apps/list.tsx
@@ -15,8 +15,8 @@ import {
RiMessage3Line,
RiRobot3Line,
} from '@remixicon/react'
-import AppCard from './AppCard'
-import NewAppCard from './NewAppCard'
+import AppCard from './app-card'
+import NewAppCard from './new-app-card'
import useAppsQueryState from './hooks/use-apps-query-state'
import { useDSLDragDrop } from './hooks/use-dsl-drag-drop'
import type { AppListResponse } from '@/models/app'
@@ -28,10 +28,17 @@ import TabSliderNew from '@/app/components/base/tab-slider-new'
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
import Input from '@/app/components/base/input'
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
-import TagManagementModal from '@/app/components/base/tag-management'
import TagFilter from '@/app/components/base/tag-management/filter'
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
-import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
+import dynamic from 'next/dynamic'
+import Empty from './empty'
+
+const TagManagementModal = dynamic(() => import('@/app/components/base/tag-management'), {
+ ssr: false,
+})
+const CreateFromDSLModal = dynamic(() => import('@/app/components/app/create-from-dsl-modal'), {
+ ssr: false,
+})
const getKey = (
pageIndex: number,
@@ -57,7 +64,7 @@ const getKey = (
return null
}
-const Apps = () => {
+const List = () => {
const { t } = useTranslation()
const router = useRouter()
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
@@ -209,7 +216,7 @@ const Apps = () => {
:
{isCurrentWorkspaceEditor
&& }
-
+
}
{isCurrentWorkspaceEditor && (
@@ -248,22 +255,4 @@ const Apps = () => {
)
}
-export default Apps
-
-function NoAppsFound() {
- const { t } = useTranslation()
- function renderDefaultCard() {
- const defaultCards = Array.from({ length: 36 }, (_, index) => (
-
- ))
- return defaultCards
- }
- return (
- <>
- {renderDefaultCard()}
-
- {t('app.newApp.noAppsFound')}
-
- >
- )
-}
+export default List
diff --git a/web/app/(commonLayout)/apps/NewAppCard.tsx b/web/app/components/apps/new-app-card.tsx
similarity index 56%
rename from web/app/(commonLayout)/apps/NewAppCard.tsx
rename to web/app/components/apps/new-app-card.tsx
index 0b42577ee..451d2ae32 100644
--- a/web/app/(commonLayout)/apps/NewAppCard.tsx
+++ b/web/app/components/apps/new-app-card.tsx
@@ -1,32 +1,38 @@
'use client'
-import { useMemo, useState } from 'react'
+import React, { useMemo, useState } from 'react'
import {
useRouter,
useSearchParams,
} from 'next/navigation'
import { useTranslation } from 'react-i18next'
-import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
-import CreateAppModal from '@/app/components/app/create-app-modal'
-import CreateFromDSLModal, { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal'
+import { CreateFromDSLModalTab } from '@/app/components/app/create-from-dsl-modal'
import { useProviderContext } from '@/context/provider-context'
import { FileArrow01, FilePlus01, FilePlus02 } from '@/app/components/base/icons/src/vender/line/files'
import cn from '@/utils/classnames'
+import dynamic from 'next/dynamic'
+
+const CreateAppModal = dynamic(() => import('@/app/components/app/create-app-modal'), {
+ ssr: false,
+})
+const CreateAppTemplateDialog = dynamic(() => import('@/app/components/app/create-app-dialog'), {
+ ssr: false,
+})
+const CreateFromDSLModal = dynamic(() => import('@/app/components/app/create-from-dsl-modal'), {
+ ssr: false,
+})
export type CreateAppCardProps = {
className?: string
onSuccess?: () => void
+ ref: React.RefObject
}
-const CreateAppCard = (
- {
- ref,
- className,
- onSuccess,
- }: CreateAppCardProps & {
- ref: React.RefObject;
- },
-) => {
+const CreateAppCard = ({
+ ref,
+ className,
+ onSuccess,
+}: CreateAppCardProps) => {
const { t } = useTranslation()
const { onPlanInfoChanged } = useProviderContext()
const searchParams = useSearchParams()
@@ -67,52 +73,58 @@ const CreateAppCard = (
- setShowNewAppModal(false)}
- onSuccess={() => {
- onPlanInfoChanged()
- if (onSuccess)
- onSuccess()
- }}
- onCreateFromTemplate={() => {
- setShowNewAppTemplateDialog(true)
- setShowNewAppModal(false)
- }}
- />
- setShowNewAppTemplateDialog(false)}
- onSuccess={() => {
- onPlanInfoChanged()
- if (onSuccess)
- onSuccess()
- }}
- onCreateFromBlank={() => {
- setShowNewAppModal(true)
- setShowNewAppTemplateDialog(false)
- }}
- />
- {
- setShowCreateFromDSLModal(false)
+ {showNewAppModal && (
+ setShowNewAppModal(false)}
+ onSuccess={() => {
+ onPlanInfoChanged()
+ if (onSuccess)
+ onSuccess()
+ }}
+ onCreateFromTemplate={() => {
+ setShowNewAppTemplateDialog(true)
+ setShowNewAppModal(false)
+ }}
+ />
+ )}
+ {showNewAppTemplateDialog && (
+ setShowNewAppTemplateDialog(false)}
+ onSuccess={() => {
+ onPlanInfoChanged()
+ if (onSuccess)
+ onSuccess()
+ }}
+ onCreateFromBlank={() => {
+ setShowNewAppModal(true)
+ setShowNewAppTemplateDialog(false)
+ }}
+ />
+ )}
+ {showCreateFromDSLModal && (
+ {
+ setShowCreateFromDSLModal(false)
- if (dslUrl)
- replace('/')
- }}
- activeTab={activeTab}
- dslUrl={dslUrl}
- onSuccess={() => {
- onPlanInfoChanged()
- if (onSuccess)
- onSuccess()
- }}
- />
+ if (dslUrl)
+ replace('/')
+ }}
+ activeTab={activeTab}
+ dslUrl={dslUrl}
+ onSuccess={() => {
+ onPlanInfoChanged()
+ if (onSuccess)
+ onSuccess()
+ }}
+ />
+ )}
)
}
CreateAppCard.displayName = 'CreateAppCard'
-export default CreateAppCard
-export { CreateAppCard }
+
+export default React.memo(CreateAppCard)
diff --git a/web/app/components/base/app-icon/index.tsx b/web/app/components/base/app-icon/index.tsx
index 003d929c8..b4724ca5d 100644
--- a/web/app/components/base/app-icon/index.tsx
+++ b/web/app/components/base/app-icon/index.tsx
@@ -1,5 +1,6 @@
'use client'
+import React from 'react'
import type { FC } from 'react'
import { init } from 'emoji-mart'
import data from '@emoji-mart/data'
@@ -71,4 +72,4 @@ const AppIcon: FC = ({
}
-export default AppIcon
+export default React.memo(AppIcon)
diff --git a/web/app/components/workflow-app/components/workflow-children.tsx b/web/app/components/workflow-app/components/workflow-children.tsx
index 6a6bbcd61..670630e57 100644
--- a/web/app/components/workflow-app/components/workflow-children.tsx
+++ b/web/app/components/workflow-app/components/workflow-children.tsx
@@ -5,10 +5,7 @@ import {
import type { EnvironmentVariable } from '@/app/components/workflow/types'
import { DSL_EXPORT_CHECK } from '@/app/components/workflow/constants'
import { useStore } from '@/app/components/workflow/store'
-import Features from '@/app/components/workflow/features'
import PluginDependency from '@/app/components/workflow/plugin-dependency'
-import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal'
-import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal'
import {
useDSL,
usePanelInteractions,
@@ -16,6 +13,17 @@ import {
import { useEventEmitterContextContext } from '@/context/event-emitter'
import WorkflowHeader from './workflow-header'
import WorkflowPanel from './workflow-panel'
+import dynamic from 'next/dynamic'
+
+const Features = dynamic(() => import('@/app/components/workflow/features'), {
+ ssr: false,
+})
+const UpdateDSLModal = dynamic(() => import('@/app/components/workflow/update-dsl-modal'), {
+ ssr: false,
+})
+const DSLExportConfirmModal = dynamic(() => import('@/app/components/workflow/dsl-export-confirm-modal'), {
+ ssr: false,
+})
const WorkflowChildren = () => {
const { eventEmitter } = useEventEmitterContextContext()
diff --git a/web/app/components/workflow-app/components/workflow-panel.tsx b/web/app/components/workflow-app/components/workflow-panel.tsx
index dd368660c..013f2834e 100644
--- a/web/app/components/workflow-app/components/workflow-panel.tsx
+++ b/web/app/components/workflow-app/components/workflow-panel.tsx
@@ -4,17 +4,35 @@ import { useStore } from '@/app/components/workflow/store'
import {
useIsChatMode,
} from '../hooks'
-import DebugAndPreview from '@/app/components/workflow/panel/debug-and-preview'
-import Record from '@/app/components/workflow/panel/record'
-import WorkflowPreview from '@/app/components/workflow/panel/workflow-preview'
-import ChatRecord from '@/app/components/workflow/panel/chat-record'
-import ChatVariablePanel from '@/app/components/workflow/panel/chat-variable-panel'
-import GlobalVariablePanel from '@/app/components/workflow/panel/global-variable-panel'
-import VersionHistoryPanel from '@/app/components/workflow/panel/version-history-panel'
import { useStore as useAppStore } from '@/app/components/app/store'
-import MessageLogModal from '@/app/components/base/message-log-modal'
import type { PanelProps } from '@/app/components/workflow/panel'
import Panel from '@/app/components/workflow/panel'
+import dynamic from 'next/dynamic'
+
+const MessageLogModal = dynamic(() => import('@/app/components/base/message-log-modal'), {
+ ssr: false,
+})
+const Record = dynamic(() => import('@/app/components/workflow/panel/record'), {
+ ssr: false,
+})
+const ChatRecord = dynamic(() => import('@/app/components/workflow/panel/chat-record'), {
+ ssr: false,
+})
+const DebugAndPreview = dynamic(() => import('@/app/components/workflow/panel/debug-and-preview'), {
+ ssr: false,
+})
+const WorkflowPreview = dynamic(() => import('@/app/components/workflow/panel/workflow-preview'), {
+ ssr: false,
+})
+const ChatVariablePanel = dynamic(() => import('@/app/components/workflow/panel/chat-variable-panel'), {
+ ssr: false,
+})
+const GlobalVariablePanel = dynamic(() => import('@/app/components/workflow/panel/global-variable-panel'), {
+ ssr: false,
+})
+const VersionHistoryPanel = dynamic(() => import('@/app/components/workflow/panel/version-history-panel'), {
+ ssr: false,
+})
const WorkflowPanelOnLeft = () => {
const { currentLogItem, setCurrentLogItem, showMessageLogModal, setShowMessageLogModal, currentLogModalActiveTab } = useAppStore(useShallow(state => ({
diff --git a/web/app/components/workflow-app/index.tsx b/web/app/components/workflow-app/index.tsx
index 761a7f29c..471d4de0d 100644
--- a/web/app/components/workflow-app/index.tsx
+++ b/web/app/components/workflow-app/index.tsx
@@ -1,3 +1,5 @@
+'use client'
+
import {
useMemo,
} from 'react'
diff --git a/web/app/components/workflow/header/header-in-normal.tsx b/web/app/components/workflow/header/header-in-normal.tsx
index 5768e6bc0..79a6509a7 100644
--- a/web/app/components/workflow/header/header-in-normal.tsx
+++ b/web/app/components/workflow/header/header-in-normal.tsx
@@ -50,8 +50,7 @@ const HeaderInNormal = ({
setShowDebugAndPreviewPanel(false)
setShowVariableInspectPanel(false)
setShowChatVariablePanel(false)
- }, [handleBackupDraft, workflowStore, handleNodeSelect, selectedNode,
- setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel])
+ }, [workflowStore, handleBackupDraft, selectedNode, handleNodeSelect, setShowWorkflowVersionHistoryPanel, setShowEnvPanel, setShowDebugAndPreviewPanel, setShowVariableInspectPanel, setShowChatVariablePanel])
return (
<>
diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx
index 771375347..8f6f8204d 100644
--- a/web/app/components/workflow/header/index.tsx
+++ b/web/app/components/workflow/header/index.tsx
@@ -4,10 +4,17 @@ import {
} from '../hooks'
import type { HeaderInNormalProps } from './header-in-normal'
import HeaderInNormal from './header-in-normal'
-import HeaderInHistory from './header-in-view-history'
import type { HeaderInRestoringProps } from './header-in-restoring'
-import HeaderInRestoring from './header-in-restoring'
import { useStore } from '../store'
+import dynamic from 'next/dynamic'
+
+const HeaderInHistory = dynamic(() => import('./header-in-view-history'), {
+ ssr: false,
+})
+const HeaderInRestoring = dynamic(() => import('./header-in-restoring'), {
+ ssr: false,
+})
+
export type HeaderProps = {
normal?: HeaderInNormalProps
restoring?: HeaderInRestoringProps
diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx
index 511c94aec..335618861 100644
--- a/web/app/components/workflow/index.tsx
+++ b/web/app/components/workflow/index.tsx
@@ -79,10 +79,14 @@ import {
} from './constants'
import { WorkflowHistoryProvider } from './workflow-history-store'
import { useEventEmitterContextContext } from '@/context/event-emitter'
-import Confirm from '@/app/components/base/confirm'
import DatasetsDetailProvider from './datasets-detail-store/provider'
import { HooksStoreContextProvider } from './hooks-store'
import type { Shape as HooksStoreShape } from './hooks-store'
+import dynamic from 'next/dynamic'
+
+const Confirm = dynamic(() => import('@/app/components/base/confirm'), {
+ ssr: false,
+})
const nodeTypes = {
[CUSTOM_NODE]: CustomNode,
diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx
index d86590335..f6425ec11 100644
--- a/web/context/modal-context.tsx
+++ b/web/context/modal-context.tsx
@@ -4,13 +4,6 @@ import type { Dispatch, SetStateAction } from 'react'
import { useCallback, useState } from 'react'
import { createContext, useContext, useContextSelector } from 'use-context-selector'
import { useRouter, useSearchParams } from 'next/navigation'
-import AccountSetting from '@/app/components/header/account-setting'
-import ApiBasedExtensionModal from '@/app/components/header/account-setting/api-based-extension-page/modal'
-import ModerationSettingModal from '@/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal'
-import ExternalDataToolModal from '@/app/components/app/configuration/tools/external-data-tool-modal'
-import AnnotationFullModal from '@/app/components/billing/annotation-full/modal'
-import ModelModal from '@/app/components/header/account-setting/model-provider-page/model-modal'
-import ExternalAPIModal from '@/app/components/datasets/external-api/external-api-modal'
import type {
ConfigurationMethodEnum,
CustomConfigurationModelFixedFields,
@@ -20,23 +13,56 @@ import type {
import {
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
} from '@/app/education-apply/constants'
-import Pricing from '@/app/components/billing/pricing'
import type { ModerationConfig, PromptVariable } from '@/models/debug'
import type {
ApiBasedExtension,
ExternalDataTool,
} from '@/models/common'
import type { CreateExternalAPIReq } from '@/app/components/datasets/external-api/declarations'
-import ModelLoadBalancingEntryModal from '@/app/components/header/account-setting/model-provider-page/model-modal/model-load-balancing-entry-modal'
import type { ModelLoadBalancingModalProps } from '@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal'
-import ModelLoadBalancingModal from '@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal'
-import OpeningSettingModal from '@/app/components/base/features/new-feature-panel/conversation-opener/modal'
import type { OpeningStatement } from '@/app/components/base/features/types'
import type { InputVar } from '@/app/components/workflow/types'
import type { UpdatePluginPayload } from '@/app/components/plugins/types'
-import UpdatePlugin from '@/app/components/plugins/update-plugin'
import { removeSpecificQueryParam } from '@/utils'
import { noop } from 'lodash-es'
+import dynamic from 'next/dynamic'
+
+const AccountSetting = dynamic(() => import('@/app/components/header/account-setting'), {
+ ssr: false,
+})
+const ApiBasedExtensionModal = dynamic(() => import('@/app/components/header/account-setting/api-based-extension-page/modal'), {
+ ssr: false,
+})
+const ModerationSettingModal = dynamic(() => import('@/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal'), {
+ ssr: false,
+})
+const ExternalDataToolModal = dynamic(() => import('@/app/components/app/configuration/tools/external-data-tool-modal'), {
+ ssr: false,
+})
+const Pricing = dynamic(() => import('@/app/components/billing/pricing'), {
+ ssr: false,
+})
+const AnnotationFullModal = dynamic(() => import('@/app/components/billing/annotation-full/modal'), {
+ ssr: false,
+})
+const ModelModal = dynamic(() => import('@/app/components/header/account-setting/model-provider-page/model-modal'), {
+ ssr: false,
+})
+const ExternalAPIModal = dynamic(() => import('@/app/components/datasets/external-api/external-api-modal'), {
+ ssr: false,
+})
+const ModelLoadBalancingModal = dynamic(() => import('@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal'), {
+ ssr: false,
+})
+const ModelLoadBalancingEntryModal = dynamic(() => import('@/app/components/header/account-setting/model-provider-page/model-modal/model-load-balancing-entry-modal'), {
+ ssr: false,
+})
+const OpeningSettingModal = dynamic(() => import('@/app/components/base/features/new-feature-panel/conversation-opener/modal'), {
+ ssr: false,
+})
+const UpdatePlugin = dynamic(() => import('@/app/components/plugins/update-plugin'), {
+ ssr: false,
+})
export type ModalState = {
payload: T
diff --git a/web/next.config.js b/web/next.config.js
index 9ce1b3564..00793bf26 100644
--- a/web/next.config.js
+++ b/web/next.config.js
@@ -12,6 +12,9 @@ const withMDX = require('@next/mdx')({
// providerImportSource: "@mdx-js/react",
},
})
+const withBundleAnalyzer = require('@next/bundle-analyzer')({
+ enabled: process.env.ANALYZE === 'true',
+})
// the default url to prevent parse url error when running jest
const hasSetWebPrefix = process.env.NEXT_PUBLIC_WEB_PREFIX
@@ -66,4 +69,4 @@ const nextConfig = {
output: 'standalone',
}
-module.exports = withMDX(nextConfig)
+module.exports = withBundleAnalyzer(withMDX(nextConfig))
diff --git a/web/package.json b/web/package.json
index 13fdc7181..414682c64 100644
--- a/web/package.json
+++ b/web/package.json
@@ -36,7 +36,8 @@
"test:watch": "jest --watch",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
- "preinstall": "npx only-allow pnpm"
+ "preinstall": "npx only-allow pnpm",
+ "analyze": "ANALYZE=true pnpm build"
},
"dependencies": {
"@babel/runtime": "^7.22.3",
@@ -159,6 +160,7 @@
"@eslint/js": "^9.20.0",
"@faker-js/faker": "^9.0.3",
"@happy-dom/jest-environment": "^17.4.4",
+ "@next/bundle-analyzer": "^15.4.1",
"@next/eslint-plugin-next": "~15.3.5",
"@rgrove/parse-xml": "^4.1.0",
"@storybook/addon-essentials": "8.5.0",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 05adb94c6..f4bc13145 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -402,6 +402,9 @@ importers:
'@happy-dom/jest-environment':
specifier: ^17.4.4
version: 17.6.3
+ '@next/bundle-analyzer':
+ specifier: ^15.4.1
+ version: 15.4.1
'@next/eslint-plugin-next':
specifier: ~15.3.5
version: 15.3.5
@@ -1316,6 +1319,10 @@ packages:
resolution: {integrity: sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==}
engines: {node: '>17.0.0'}
+ '@discoveryjs/json-ext@0.5.7':
+ resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
+ engines: {node: '>=10.0.0'}
+
'@emnapi/core@1.4.4':
resolution: {integrity: sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g==}
@@ -2099,6 +2106,9 @@ packages:
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
+ '@next/bundle-analyzer@15.4.1':
+ resolution: {integrity: sha512-O5R3iPLR3/oQWFIXl+Mnd02IyhvWBterTlXcceIGw29QHWL/gjvyO0eIVEvrJPS7zzE6/NSu1TiSVgi8mxotlw==}
+
'@next/env@15.3.5':
resolution: {integrity: sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g==}
@@ -2459,6 +2469,9 @@ packages:
webpack-plugin-serve:
optional: true
+ '@polka/url@1.0.0-next.29':
+ resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+
'@react-aria/focus@3.20.5':
resolution: {integrity: sha512-JpFtXmWQ0Oca7FcvkqgjSyo6xEP7v3oQOLUId6o0xTvm4AD5W0mU2r3lYrbhsJ+XxdUUX4AVR5473sZZ85kU4A==}
peerDependencies:
@@ -4341,6 +4354,9 @@ packages:
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
+ debounce@1.2.1:
+ resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
+
debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -4498,6 +4514,9 @@ packages:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'}
+ duplexer@0.1.2:
+ resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
+
echarts-for-react@3.0.2:
resolution: {integrity: sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA==}
peerDependencies:
@@ -5250,6 +5269,10 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+ gzip-size@6.0.0:
+ resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
+ engines: {node: '>=10'}
+
hachure-fill@0.5.2:
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
@@ -5563,6 +5586,10 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
+ is-plain-object@5.0.0:
+ resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+ engines: {node: '>=0.10.0'}
+
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
@@ -6299,6 +6326,10 @@ packages:
monaco-editor@0.52.2:
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
+ mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -6454,6 +6485,10 @@ packages:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
+ opener@1.5.2:
+ resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
+ hasBin: true
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -7358,6 +7393,10 @@ packages:
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
+ sirv@2.0.4:
+ resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
+ engines: {node: '>= 10'}
+
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
@@ -7679,6 +7718,10 @@ packages:
resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ totalist@3.0.1:
+ resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+ engines: {node: '>=6'}
+
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -7994,6 +8037,11 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
+ webpack-bundle-analyzer@4.10.1:
+ resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
+ engines: {node: '>= 10.13.0'}
+ hasBin: true
+
webpack-code-inspector-plugin@0.18.3:
resolution: {integrity: sha512-3782rsJhBnRiw0IpR6EqnyGDQoiSq0CcGeLJ52rZXlszYCe8igXtcujq7OhI0byaivWQ1LW7sXKyMEoVpBhq0w==}
@@ -8064,6 +8112,18 @@ packages:
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+ ws@7.5.10:
+ resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
+ engines: {node: '>=8.3.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ^5.0.2
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
ws@8.18.3:
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
engines: {node: '>=10.0.0'}
@@ -9100,6 +9160,8 @@ snapshots:
'@dagrejs/graphlib@2.2.4': {}
+ '@discoveryjs/json-ext@0.5.7': {}
+
'@emnapi/core@1.4.4':
dependencies:
'@emnapi/wasi-threads': 1.0.3
@@ -10072,6 +10134,13 @@ snapshots:
'@tybys/wasm-util': 0.10.0
optional: true
+ '@next/bundle-analyzer@15.4.1':
+ dependencies:
+ webpack-bundle-analyzer: 4.10.1
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
'@next/env@15.3.5': {}
'@next/eslint-plugin-next@15.3.5':
@@ -10361,6 +10430,8 @@ snapshots:
type-fest: 2.19.0
webpack-hot-middleware: 2.26.1
+ '@polka/url@1.0.0-next.29': {}
+
'@react-aria/focus@3.20.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@react-aria/interactions': 3.25.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -12629,6 +12700,8 @@ snapshots:
dayjs@1.11.13: {}
+ debounce@1.2.1: {}
+
debug@3.2.7:
dependencies:
ms: 2.1.3
@@ -12756,6 +12829,8 @@ snapshots:
dotenv@16.6.1: {}
+ duplexer@0.1.2: {}
+
echarts-for-react@3.0.2(echarts@5.6.0)(react@19.1.0):
dependencies:
echarts: 5.6.0
@@ -13775,6 +13850,10 @@ snapshots:
graphemer@1.4.0: {}
+ gzip-size@6.0.0:
+ dependencies:
+ duplexer: 0.1.2
+
hachure-fill@0.5.2: {}
happy-dom@17.6.3:
@@ -14149,6 +14228,8 @@ snapshots:
is-plain-obj@4.1.0: {}
+ is-plain-object@5.0.0: {}
+
is-stream@2.0.1: {}
is-stream@3.0.0: {}
@@ -15373,6 +15454,8 @@ snapshots:
monaco-editor@0.52.2: {}
+ mrmime@2.0.1: {}
+
ms@2.1.3: {}
mz@2.7.0:
@@ -15536,6 +15619,8 @@ snapshots:
is-docker: 2.2.1
is-wsl: 2.2.0
+ opener@1.5.2: {}
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -16599,6 +16684,12 @@ snapshots:
dependencies:
is-arrayish: 0.3.2
+ sirv@2.0.4:
+ dependencies:
+ '@polka/url': 1.0.0-next.29
+ mrmime: 2.0.1
+ totalist: 3.0.1
+
sisteransi@1.0.5: {}
size-sensor@1.0.2: {}
@@ -16913,6 +17004,8 @@ snapshots:
dependencies:
eslint-visitor-keys: 3.4.3
+ totalist@3.0.1: {}
+
tr46@0.0.3:
optional: true
@@ -17246,6 +17339,25 @@ snapshots:
webidl-conversions@7.0.0: {}
+ webpack-bundle-analyzer@4.10.1:
+ dependencies:
+ '@discoveryjs/json-ext': 0.5.7
+ acorn: 8.15.0
+ acorn-walk: 8.3.4
+ commander: 7.2.0
+ debounce: 1.2.1
+ escape-string-regexp: 4.0.0
+ gzip-size: 6.0.0
+ html-escaper: 2.0.2
+ is-plain-object: 5.0.0
+ opener: 1.5.2
+ picocolors: 1.1.1
+ sirv: 2.0.4
+ ws: 7.5.10
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
+
webpack-code-inspector-plugin@0.18.3:
dependencies:
code-inspector-core: 0.18.3
@@ -17348,6 +17460,8 @@ snapshots:
imurmurhash: 0.1.4
signal-exit: 3.0.7
+ ws@7.5.10: {}
+
ws@8.18.3: {}
xml-name-validator@4.0.0: {}