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 ( +
+

{t('app.join')}

+

{t('app.communityIntro')}

+
+ + + + + + +
+
+ ) +} + +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: {}