fix(web): reduce chart and syntax highlighting bundles (#42845)

This commit is contained in:
Stephen Zhou authored and GitHub committed 2026-09-23 12:32:31 +00:00
1 parent 200da319f2
commit e9550e50d0
75 files changed
+322 -136

No files matched your search

+18 -2
View File
@@ -922,7 +922,15 @@ export const lintConfig = {
message: 'Do not import next/font. Use the project font styles instead.',
},
{
group: ['next/*', '!next/font', '!next/font/*', '!next/image', '!next/image/*'],
// next/dynamic must be imported directly for compiler-generated preload metadata.
group: [
'next/*',
'!next/dynamic',
'!next/font',
'!next/font/*',
'!next/image',
'!next/image/*',
],
message:
'Import Next APIs from the corresponding @/next/* module instead of next/*.',
},
@@ -989,7 +997,15 @@ export const lintConfig = {
message: 'Do not import next/font. Use the project font styles instead.',
},
{
group: ['next/*', '!next/font', '!next/font/*', '!next/image', '!next/image/*'],
// next/dynamic must be imported directly for compiler-generated preload metadata.
group: [
'next/*',
'!next/dynamic',
'!next/font',
'!next/font/*',
'!next/image',
'!next/image/*',
],
message:
'Import Next APIs from the corresponding @/next/* module instead of next/*.',
},
+1 -1
View File
@@ -1,8 +1,8 @@
'use client'
import dynamic from 'next/dynamic'
import { Pricing } from '@/app/components/billing/pricing'
import { SettingsModal } from '@/app/components/header/account-setting/settings-modal'
import dynamic from '@/next/dynamic'
const InSiteMessageNotification = dynamic(
() => import('@/app/components/app/in-site-message/notification'),
@@ -5,7 +5,7 @@ import * as React from 'react'
import { AppModeEnum } from '@/types/app'
import AppInfoModals from '../app-info-modals'
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: (loader: () => Promise<{ default: React.ComponentType }>) => {
const LazyComp = React.lazy(loader)
return function DynamicWrapper(props: Record<string, unknown>) {
@@ -15,11 +15,11 @@ import {
import { Button } from '@langgenius/dify-ui/button'
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group'
import dynamic from 'next/dynamic'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { AppExportConfirmContent } from '@/app/components/app/export-confirm-modal'
import dynamic from '@/next/dynamic'
const SwitchAppModal = dynamic(() => import('@/app/components/app/switch-app-modal'), {
ssr: false,
+1 -1
View File
@@ -8,6 +8,7 @@ import type { DeploymentDialogRequest } from './types'
import type { DeploymentVersion } from './utils/version'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import dynamic from 'next/dynamic'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocale } from '#i18n'
@@ -17,7 +18,6 @@ import { getEnterpriseDocUrl } from '@/context/i18n'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { getDocLanguage } from '@/i18n/language'
import dynamic from '@/next/dynamic'
import { AppModeEnum } from '@/types/app'
import { getAppACLCapabilities } from '@/utils/permission'
import { BuiltInEnvironmentCard } from './built-in-environment-card'
@@ -19,10 +19,10 @@ import {
SelectItemText,
SelectTrigger,
} from '@langgenius/dify-ui/select'
import dynamic from 'next/dynamic'
import { memo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { isLLMEnvironmentVariableValue } from '@/app/components/workflow/llm-environment-variable'
import dynamic from '@/next/dynamic'
import { resolveEnvironmentVariableSelection } from './utils/workflow-deployment-input'
const LLMEnvironmentVariableValueField = dynamic(
@@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'
import Chart, { MessagesChart } from '../app-chart'
const reactEChartsMock = vi.fn()
vi.mock('echarts-for-react', () => ({
vi.mock('echarts-for-react/esm/core', () => ({
default: (props: { option: unknown; opts?: unknown }) => {
reactEChartsMock(props)
return <div role="img" aria-label="Chart" />
@@ -6,9 +6,10 @@ import type { FC } from 'react'
import type { ChartRow } from './app-chart-utils'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useQuery } from '@tanstack/react-query'
import ReactECharts from 'echarts-for-react'
import ReactECharts from 'echarts-for-react/esm/core'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { echarts } from '@/app/components/base/line-chart/echarts'
import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
import { consoleQuery } from '@/service/console'
import {
@@ -131,6 +132,7 @@ const Chart: React.FC<IChartProps> = ({
</div>
<div className="h-60 shrink-0 px-6 pb-4">
<ReactECharts
echarts={echarts}
option={options}
opts={ECHARTS_RENDER_OPTIONS}
style={{ height: '100%', width: '100%' }}
@@ -247,7 +247,7 @@ vi.mock('@/utils/time', () => ({
}))
// Mock dynamic imports
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: (importFn: () => Promise<unknown>) => {
void importFn().catch(() => {})
const fnString = importFn.toString()
@@ -38,7 +38,7 @@ vi.mock('@/next/navigation', () => ({
useParams: () => ({}),
useSearchParams: () => search.params,
}))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: (loader: () => Promise<{ default: React.ComponentType }>) => {
const LazyComponent = React.lazy(loader)
return function Dynamic(props: Record<string, unknown>) {
@@ -262,7 +262,7 @@ vi.mock('@/hooks/use-pay', () => ({
CheckModal: () => null,
}))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: (importFn: () => Promise<unknown>) => {
const fnString = importFn.toString()
@@ -40,6 +40,7 @@ import { Toggle } from '@langgenius/dify-ui/toggle'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useMutation, useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import dynamic from 'next/dynamic'
import { useCallback, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useExportAppDsl, useExportWorkflowAppDsl } from '@/app/components/app/use-export-app-dsl'
@@ -54,7 +55,6 @@ import { userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
import { AccessMode } from '@/models/access-control'
import dynamic from '@/next/dynamic'
import { useRouter } from '@/next/navigation'
import { useGetUserCanAccessApp } from '@/service/access-control/use-app-access-control'
import { consoleClient, consoleQuery } from '@/service/console'
@@ -2,7 +2,7 @@
import type { AppListUrlQuery } from './query-params'
import { zPostAppsBody } from '@dify/contracts/api/console/apps/zod.gen'
import dynamic from '@/next/dynamic'
import dynamic from 'next/dynamic'
type AppListCategory = AppListUrlQuery['category']
@@ -1,6 +1,6 @@
'use client'
import dynamic from '@/next/dynamic'
import dynamic from 'next/dynamic'
const TagManagementModal = dynamic(
() =>
+1 -1
View File
@@ -4,6 +4,7 @@ import type { CreateAppModalProps } from '../explore/create-app-modal'
import type { TrackCreateAppParams } from '@/utils/create-app-tracking'
import { noop, useQueryClient } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import dynamic from 'next/dynamic'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { EducationExpireNotice } from '@/app/education/expire-notice'
@@ -12,7 +13,6 @@ import AppListContext from '@/context/app-list-context'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { useImportDSL } from '@/hooks/use-import-dsl'
import { DSLImportMode } from '@/models/app'
import dynamic from '@/next/dynamic'
import { useRouter, useSearchParams } from '@/next/navigation'
import { consoleClient, consoleQuery } from '@/service/console'
import { trackCreateApp } from '@/utils/create-app-tracking'
@@ -1,7 +1,7 @@
'use client'
import type { IAmplitudeProps } from './AmplitudeProvider'
import dynamic from '@/next/dynamic'
import dynamic from 'next/dynamic'
const AmplitudeProvider = dynamic(
() => import('./AmplitudeProvider').then((module) => module.AmplitudeProvider),
@@ -38,7 +38,7 @@ const mockDynamic = vi.hoisted(() =>
const mockPdfPreview = vi.hoisted(() => vi.fn(() => null))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: mockDynamic,
}))
@@ -1,6 +1,6 @@
'use client'
import dynamic from '@/next/dynamic'
import dynamic from 'next/dynamic'
type DynamicPdfPreviewProps = {
url: string
@@ -0,0 +1,43 @@
import { buildChartOptions as buildOverviewOptions } from '@/app/components/app/overview/app-chart-utils'
import { buildChartOptions as buildMonitoringOptions } from '@/features/agent-v2/agent-detail/monitoring/chart-utils'
import { echarts } from '../echarts'
it.each([
[
'overview',
buildOverviewOptions({
statistics: [
{ date: '2026-01-01', count: 3 },
{ date: '2026-01-02', count: 7 },
],
chartType: 'conversations',
yField: 'count',
}),
],
[
'monitoring',
buildMonitoringOptions({
rows: [
{ date: '2026-01-01', count: 3 },
{ date: '2026-01-02', count: 7 },
],
chartType: 'conversations',
valueKey: 'count',
}),
],
] as const)('renders the %s dataset with the registered line-chart runtime', (_name, option) => {
const chart = echarts.init(null, undefined, {
renderer: 'svg',
ssr: true,
width: 400,
height: 240,
})
try {
chart.setOption({ ...option, animation: false })
const svg = chart.renderToSVGString()
expect(svg).toContain('stroke="rgb(6,148,162)"')
expect(svg).toContain('Jan')
} finally {
chart.dispose()
}
})
@@ -0,0 +1,19 @@
import { LineChart } from 'echarts/charts'
import { DatasetComponent, GridComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { LegacyGridContainLabel } from 'echarts/features'
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers'
// Overview uses SVG; Agent monitoring retains its canvas renderer.
echarts.use([
LineChart,
DatasetComponent,
GridComponent,
TooltipComponent,
LegacyGridContainLabel,
CanvasRenderer,
SVGRenderer,
])
// oxlint-disable-next-line no-barrel-files/no-barrel-files -- Consumers need the runtime configured by this module's chart and renderer registrations.
export { echarts }
@@ -1,7 +1,8 @@
import type { JSX } from 'react'
import type { BundledTheme } from 'shiki/bundle/web'
import type { CodeTheme } from './shiki-highlight'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Toggle } from '@langgenius/dify-ui/toggle'
import dynamic from 'next/dynamic'
import {
lazy,
memo,
@@ -16,7 +17,6 @@ import {
import CopyIcon from '@/app/components/base/copy-icon'
import ErrorBoundary from '@/app/components/base/markdown/error-boundary'
import useTheme from '@/hooks/use-theme'
import dynamic from '@/next/dynamic'
import { Theme } from '@/types/app'
import SVGRenderer from '../svg-gallery' // Assumes svg-gallery.tsx is in /base directory
import { highlightCode } from './shiki-highlight'
@@ -79,7 +79,7 @@ const ShikiCodeBlock = memo(
}: {
code: string
language: string
theme: BundledTheme
theme: CodeTheme
initial?: JSX.Element
}) => {
const [nodes, setNodes] = useState(initial)
@@ -1,14 +1,28 @@
import type { JSX } from 'react'
import type { BundledTheme } from 'shiki/bundle/web'
import { toJsxRuntime } from 'hast-util-to-jsx-runtime'
import { Fragment } from 'react'
import { jsx, jsxs } from 'react/jsx-runtime'
import { bundledLanguages, getSingletonHighlighter } from 'shiki/bundle/web'
import { createBundledHighlighter, createSingletonShorthands } from 'shiki/core'
import { createOnigurumaEngine } from 'shiki/engine/oniguruma'
import { codeLanguages } from './shiki-languages'
const createHighlighter = createBundledHighlighter({
langs: codeLanguages,
themes: {
'github-dark': () => import('shiki/themes/github-dark.mjs'),
'github-light': () => import('shiki/themes/github-light.mjs'),
},
engine: () => createOnigurumaEngine(import('shiki/wasm')),
})
const { getSingletonHighlighter } = createSingletonShorthands(createHighlighter)
export type CodeTheme = 'github-dark' | 'github-light'
type HighlightCodeOptions = {
code: string
language: string
theme: BundledTheme
theme: CodeTheme
}
export const highlightCode = async ({
@@ -18,7 +32,7 @@ export const highlightCode = async ({
}: HighlightCodeOptions): Promise<JSX.Element> => {
const normalizedLanguage = language.trim().toLowerCase()
const lang =
normalizedLanguage === 'dotenv' || Object.hasOwn(bundledLanguages, normalizedLanguage)
normalizedLanguage === 'dotenv' || Object.hasOwn(codeLanguages, normalizedLanguage)
? normalizedLanguage
: 'text'
// README fences may name languages outside the web bundle. Load dotenv on
@@ -0,0 +1,82 @@
// Keep the existing Shiki web language set and aliases without importing its all-theme bundle.
export const codeLanguages = {
'angular-html': () => import('shiki/langs/angular-html.mjs'),
'angular-ts': () => import('shiki/langs/angular-ts.mjs'),
astro: () => import('shiki/langs/astro.mjs'),
blade: () => import('shiki/langs/blade.mjs'),
c: () => import('shiki/langs/c.mjs'),
coffee: () => import('shiki/langs/coffee.mjs'),
coffeescript: () => import('shiki/langs/coffee.mjs'),
cpp: () => import('shiki/langs/cpp.mjs'),
'c++': () => import('shiki/langs/cpp.mjs'),
css: () => import('shiki/langs/css.mjs'),
csv: () => import('shiki/langs/csv.mjs'),
glsl: () => import('shiki/langs/glsl.mjs'),
graphql: () => import('shiki/langs/graphql.mjs'),
gql: () => import('shiki/langs/graphql.mjs'),
haml: () => import('shiki/langs/haml.mjs'),
handlebars: () => import('shiki/langs/handlebars.mjs'),
hbs: () => import('shiki/langs/handlebars.mjs'),
html: () => import('shiki/langs/html.mjs'),
'html-derivative': () => import('shiki/langs/html-derivative.mjs'),
http: () => import('shiki/langs/http.mjs'),
hurl: () => import('shiki/langs/hurl.mjs'),
imba: () => import('shiki/langs/imba.mjs'),
java: () => import('shiki/langs/java.mjs'),
javascript: () => import('shiki/langs/javascript.mjs'),
js: () => import('shiki/langs/javascript.mjs'),
cjs: () => import('shiki/langs/javascript.mjs'),
mjs: () => import('shiki/langs/javascript.mjs'),
jinja: () => import('shiki/langs/jinja.mjs'),
jison: () => import('shiki/langs/jison.mjs'),
json: () => import('shiki/langs/json.mjs'),
json5: () => import('shiki/langs/json5.mjs'),
jsonc: () => import('shiki/langs/jsonc.mjs'),
jsonl: () => import('shiki/langs/jsonl.mjs'),
jsx: () => import('shiki/langs/jsx.mjs'),
julia: () => import('shiki/langs/julia.mjs'),
jl: () => import('shiki/langs/julia.mjs'),
less: () => import('shiki/langs/less.mjs'),
markdown: () => import('shiki/langs/markdown.mjs'),
md: () => import('shiki/langs/markdown.mjs'),
marko: () => import('shiki/langs/marko.mjs'),
mdc: () => import('shiki/langs/mdc.mjs'),
mdx: () => import('shiki/langs/mdx.mjs'),
php: () => import('shiki/langs/php.mjs'),
postcss: () => import('shiki/langs/postcss.mjs'),
pug: () => import('shiki/langs/pug.mjs'),
jade: () => import('shiki/langs/pug.mjs'),
python: () => import('shiki/langs/python.mjs'),
py: () => import('shiki/langs/python.mjs'),
r: () => import('shiki/langs/r.mjs'),
regexp: () => import('shiki/langs/regexp.mjs'),
regex: () => import('shiki/langs/regexp.mjs'),
sass: () => import('shiki/langs/sass.mjs'),
scss: () => import('shiki/langs/scss.mjs'),
shellscript: () => import('shiki/langs/shellscript.mjs'),
bash: () => import('shiki/langs/shellscript.mjs'),
sh: () => import('shiki/langs/shellscript.mjs'),
shell: () => import('shiki/langs/shellscript.mjs'),
zsh: () => import('shiki/langs/shellscript.mjs'),
smithy: () => import('shiki/langs/smithy.mjs'),
sql: () => import('shiki/langs/sql.mjs'),
stylus: () => import('shiki/langs/stylus.mjs'),
styl: () => import('shiki/langs/stylus.mjs'),
svelte: () => import('shiki/langs/svelte.mjs'),
'ts-tags': () => import('shiki/langs/ts-tags.mjs'),
lit: () => import('shiki/langs/ts-tags.mjs'),
tsx: () => import('shiki/langs/tsx.mjs'),
typescript: () => import('shiki/langs/typescript.mjs'),
ts: () => import('shiki/langs/typescript.mjs'),
cts: () => import('shiki/langs/typescript.mjs'),
mts: () => import('shiki/langs/typescript.mjs'),
vue: () => import('shiki/langs/vue.mjs'),
'vue-html': () => import('shiki/langs/vue-html.mjs'),
'vue-vine': () => import('shiki/langs/vue-vine.mjs'),
wasm: () => import('shiki/langs/wasm.mjs'),
wgsl: () => import('shiki/langs/wgsl.mjs'),
wit: () => import('shiki/langs/wit.mjs'),
xml: () => import('shiki/langs/xml.mjs'),
yaml: () => import('shiki/langs/yaml.mjs'),
yml: () => import('shiki/langs/yaml.mjs'),
}
@@ -7,7 +7,7 @@ const { mockReactMarkdownWrapper } = vi.hoisted(() => ({
mockReactMarkdownWrapper: vi.fn(),
}))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => {
const MockStreamdownWrapper = (props: { latexContent: string }) => {
mockReactMarkdownWrapper(props)
+1 -1
View File
@@ -1,8 +1,8 @@
import type { SimplePluginInfo, StreamdownWrapperProps } from './streamdown-wrapper'
import { cn } from '@langgenius/dify-ui/cn'
import { flow } from 'es-toolkit/compat'
import dynamic from 'next/dynamic'
import { memo, useMemo } from 'react'
import dynamic from '@/next/dynamic'
import { preprocessLaTeX, preprocessThinkTag } from './markdown-utils'
const StreamdownWrapper = dynamic(() => import('./streamdown-wrapper'), { ssr: false })
@@ -1,6 +1,7 @@
import type { ComponentType } from 'react'
import type { Components, StreamdownProps } from 'streamdown'
import { createMathPlugin } from '@streamdown/math'
import dynamic from 'next/dynamic'
import { memo, useMemo } from 'react'
import RemarkBreaks from 'remark-breaks'
import { defaultRehypePlugins, defaultRemarkPlugins, Streamdown } from 'streamdown'
@@ -17,7 +18,6 @@ import {
VideoBlock,
} from '@/app/components/base/markdown-blocks'
import { ALLOW_INLINE_STYLES, ENABLE_SINGLE_DOLLAR_LATEX } from '@/config'
import dynamic from '@/next/dynamic'
import { customUrlTransform } from './markdown-utils'
import 'katex/dist/katex.min.css'
@@ -1,6 +1,33 @@
@property --simple-pie-chart-percentage {
syntax: '<number>';
inherits: false;
initial-value: 0;
}
.simplePieChart {
border-radius: 50%;
background:
radial-gradient(closest-side, transparent 83%, var(--simple-pie-chart-stroke) 83%),
conic-gradient(
var(--simple-pie-chart-color) calc(var(--simple-pie-chart-percentage) * 1%),
#fff 0
);
animation: reveal var(--simple-pie-chart-duration) cubic-bezier(0.215, 0.61, 0.355, 1);
transition: --simple-pie-chart-percentage 500ms cubic-bezier(0.215, 0.61, 0.355, 1);
box-shadow:
0 0 5px -3px rgb(from var(--simple-pie-chart-color) r g b / 0.1),
0.5px 0.5px 3px 0 rgb(from var(--simple-pie-chart-color) r g b / 0.3);
}
@keyframes reveal {
from {
--simple-pie-chart-percentage: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.simplePieChart {
animation: none;
transition: none;
}
}
@@ -1,8 +1,6 @@
import type { EChartsOption } from 'echarts'
import type { CSSProperties } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import ReactECharts from 'echarts-for-react'
import { memo, useMemo } from 'react'
import { memo } from 'react'
import style from './index.module.css'
type SimplePieChartProps = {
@@ -22,50 +20,15 @@ const SimplePieChart = ({
animationDuration,
className,
}: SimplePieChartProps) => {
const option: EChartsOption = useMemo(
() => ({
series: [
{
type: 'pie',
radius: ['83%', '100%'],
animation: false,
data: [{ value: 100, itemStyle: { color: stroke } }],
emphasis: {
disabled: true,
},
labelLine: {
show: false,
},
cursor: 'default',
},
{
type: 'pie',
radius: '83%',
animationDuration: animationDuration ?? 600,
data: [
{ value: percentage, itemStyle: { color: fill } },
{ value: 100 - percentage, itemStyle: { color: '#fff' } },
],
emphasis: {
disabled: true,
},
labelLine: {
show: false,
},
cursor: 'default',
},
],
}),
[stroke, fill, percentage, animationDuration],
)
return (
<ReactECharts
option={option}
<div
className={cn(style.simplePieChart, className)}
style={
{
'--simple-pie-chart-color': fill,
'--simple-pie-chart-stroke': stroke,
'--simple-pie-chart-percentage': Math.min(100, Math.max(0, percentage)),
'--simple-pie-chart-duration': `${animationDuration ?? 600}ms`,
width: size,
height: size,
} as CSSProperties
@@ -64,7 +64,7 @@ vi.mock('@/app/components/datasets/common/document-file-icon', () => ({
}))
// Mock SimplePieChart
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => {
const Component = ({ percentage }: { percentage: number }) => (
<div data-testid="pie-chart">{percentage}%</div>
@@ -18,7 +18,7 @@ vi.mock('@/types/app', () => ({
}))
// Mock SimplePieChart with dynamic import handling
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => {
const DynamicComponent = ({
percentage,
@@ -2,11 +2,11 @@
import type { CustomFile as File, FileItem } from '@/models/datasets'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { RiDeleteBinLine, RiErrorWarningFill } from '@remixicon/react'
import dynamic from 'next/dynamic'
import { useId, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import DocumentFileIcon from '@/app/components/datasets/common/document-file-icon'
import useTheme from '@/hooks/use-theme'
import dynamic from '@/next/dynamic'
import { Theme } from '@/types/app'
import { formatFileSize, getFileExtension } from '@/utils/format'
import { PROGRESS_COMPLETE, PROGRESS_ERROR } from '../constants'
@@ -24,7 +24,7 @@ vi.mock('@/app/components/datasets/common/document-file-icon', () => ({
}))
// Mock SimplePieChart
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => {
const Component = ({ percentage }: { percentage: number }) => (
<div data-testid="pie-chart">{percentage}%</div>
@@ -17,7 +17,7 @@ vi.mock('@/types/app', () => ({
}))
// Mock SimplePieChart with dynamic import handling
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => {
const DynamicComponent = ({
percentage,
@@ -1,11 +1,11 @@
import type { CustomFile as File, FileItem } from '@/models/datasets'
import { cn } from '@langgenius/dify-ui/cn'
import { RiDeleteBinLine, RiErrorWarningFill } from '@remixicon/react'
import dynamic from 'next/dynamic'
import { useMemo } from 'react'
import DocumentFileIcon from '@/app/components/datasets/common/document-file-icon'
import { getFileType } from '@/app/components/datasets/common/image-uploader/utils'
import useTheme from '@/hooks/use-theme'
import dynamic from '@/next/dynamic'
import { Theme } from '@/types/app'
import { formatFileSize } from '@/utils/format'
import { PROGRESS_ERROR } from '../constants'
@@ -1,16 +1,16 @@
'use client'
import { IS_DEV } from '@/config'
import dynamic from '@/next/dynamic'
import dynamic from 'next/dynamic'
const Agentation = dynamic(() => import('agentation').then((module) => module.Agentation), {
ssr: false,
})
const Agentation =
process.env.NODE_ENV === 'development'
? dynamic(() => import('agentation').then((module) => module.Agentation), { ssr: false })
: null
const IS_AGENTATION_ENABLED = process.env.NEXT_PUBLIC_ENABLE_AGENTATION !== 'false'
export function AgentationLoader() {
if (!IS_DEV || !IS_AGENTATION_ENABLED) return null
if (!Agentation || !IS_AGENTATION_ENABLED) return null
return <Agentation />
}
@@ -2,6 +2,7 @@
import type { AppMode } from '@dify/contracts/api/console/installed-apps/types.gen'
import type { AppData } from '@/models/share'
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
import dynamic from 'next/dynamic'
import * as React from 'react'
import { useEffect } from 'react'
import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
@@ -9,7 +10,6 @@ import TextGenerationApp from '@/app/components/share/text-generation'
import { useWebAppStore } from '@/context/web-app-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AccessMode } from '@/models/access-control'
import dynamic from '@/next/dynamic'
import { useGetUserCanAccessApp } from '@/service/access-control/use-app-access-control'
import { consoleQuery } from '@/service/console'
import AppUnavailable from '../../base/app-unavailable'
@@ -1,8 +1,8 @@
'use client'
import type { SettingsDestination } from './query-params'
import dynamic from 'next/dynamic'
import { useQueryState } from 'nuqs'
import dynamic from '@/next/dynamic'
import {
isAccountSettingDestination,
isIntegrationSettingDestination,
@@ -21,7 +21,7 @@ const { dynamicComponent } = vi.hoisted(() => {
}
})
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => dynamicComponent(),
}))
@@ -1,8 +1,8 @@
import type { PanelProps } from '@/app/components/workflow/panel'
import dynamic from 'next/dynamic'
import { memo, useMemo } from 'react'
import Panel from '@/app/components/workflow/panel'
import { useStore } from '@/app/components/workflow/store'
import dynamic from '@/next/dynamic'
const Record = dynamic(() => import('@/app/components/workflow/panel/record'), {
ssr: false,
@@ -115,7 +115,7 @@ vi.mock('@/next/navigation', () => ({
useSearchParams: () => new URLSearchParams(''),
}))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => {
return function MockDynamicComponent(props: {
show: boolean
@@ -103,8 +103,8 @@ vi.mock('@/next/navigation', () => ({
useSearchParams: () => new URLSearchParams(),
}))
vi.mock('@/next/dynamic', () => ({
default: () => () => null,
vi.mock('@/features/tag-management/components/tag-management-modal', () => ({
TagManagementModal: () => null,
}))
vi.mock('@/hooks/use-document-title', () => ({
+1 -1
View File
@@ -14,6 +14,7 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { useDebounce } from 'ahooks'
import { useAtomValue } from 'jotai'
import dynamic from 'next/dynamic'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SearchInput } from '@/app/components/base/search-input'
@@ -21,7 +22,6 @@ import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { currentWorkspaceLoadingAtom } from '@/context/workspace-state'
import { TagFilter } from '@/features/tag-management/components/tag-filter'
import useDocumentTitle from '@/hooks/use-document-title'
import dynamic from '@/next/dynamic'
import Link from '@/next/link'
import { useInfiniteSnippetList } from '@/service/use-snippets'
import CreatorsFilter from '../apps/creators-filter'
@@ -2,10 +2,10 @@
import type { PanelProps } from '@/app/components/workflow/panel'
import type { SnippetInputField } from '@/models/snippet'
import dynamic from 'next/dynamic'
import { memo, useMemo } from 'react'
import Panel from '@/app/components/workflow/panel'
import { useStore } from '@/app/components/workflow/store'
import dynamic from '@/next/dynamic'
const Record = dynamic(() => import('@/app/components/workflow/panel/record'), {
ssr: false,
@@ -177,7 +177,7 @@ vi.mock('@/app/components/workflow-app/components/workflow-panel', () => ({
default: () => <div data-testid="workflow-panel">workflow-panel</div>,
}))
vi.mock('@/next/dynamic', async () => {
vi.mock('next/dynamic', async () => {
const ReactModule = await import('react')
return {
@@ -75,7 +75,7 @@ vi.mock('@/app/components/workflow/panel', () => ({
),
}))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: (loader: () => Promise<{ default: React.ComponentType<Record<string, unknown>> }>) => {
const LazyComp = React.lazy(loader)
return function DynamicWrapper(props: Record<string, unknown>) {
@@ -3,6 +3,7 @@ import type {
TriggerDefaultValue,
} from '@/app/components/workflow/block-selector/types'
import type { ExportSecretEnvironmentVariable } from '@/app/components/workflow/export-secret-env-event'
import dynamic from 'next/dynamic'
import { memo, useCallback, useState } from 'react'
import { useStoreApi } from 'reactflow'
import { START_INITIAL_POSITION } from '@/app/components/workflow/constants'
@@ -17,7 +18,6 @@ import { useStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
import { generateNewNode } from '@/app/components/workflow/utils'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import dynamic from '@/next/dynamic'
import { useAutoOnboarding } from '../hooks/use-auto-onboarding'
import { useAvailableNodesMetaData } from '../hooks/use-available-nodes-meta-data'
import WorkflowHeader from './workflow-header'
@@ -1,11 +1,11 @@
import type { PanelProps } from '@/app/components/workflow/panel'
import dynamic from 'next/dynamic'
import { memo, useMemo } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { useStore as useAppStore } from '@/app/components/app/store'
import Panel from '@/app/components/workflow/panel'
import CommentsPanel from '@/app/components/workflow/panel/comments-panel'
import { useStore } from '@/app/components/workflow/store'
import dynamic from '@/next/dynamic'
import { useIsChatMode } from '../hooks/use-is-chat-mode'
const MessageLogModal = dynamic(() => import('@/app/components/base/message-log-modal'), {
@@ -157,7 +157,7 @@ vi.mock('@/context/workspace-state', async () => {
}))
})
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => () => null,
}))
@@ -49,7 +49,7 @@ it('selects the built-in local file datasource directly with its file extensions
const provider = createDatasourceProvider()
render(<DataSources searchText="" onSelect={onSelect} dataSources={[provider]} />)
await user.click(screen.getByRole('button', { name: 'File Source' }))
await user.click(await screen.findByRole('button', { name: 'File Source' }))
const action = screen.getByRole('button', { name: 'Local File' })
expect(action).toHaveAccessibleDescription('Load local files')
action.focus()
@@ -94,7 +94,7 @@ it('keeps unauthorized installed providers selectable and falls back from null l
const onSelect = vi.fn()
render(<DataSources searchText="pages" onSelect={onSelect} dataSources={[provider]} />)
expect(screen.getByRole('button', { name: 'Drive source' })).toHaveAttribute(
expect(await screen.findByRole('button', { name: 'Drive source' })).toHaveAttribute(
'aria-expanded',
'true',
)
@@ -140,14 +140,14 @@ it('filters by raw provider or action name and retains each matching provider gr
dataSources={[provider, namedProvider('other')]}
/>,
)
expect(screen.getByRole('button', { name: 'Matched action' })).toBeInTheDocument()
expect(await screen.findByRole('button', { name: 'Matched action' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Sibling action' })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'other' })).not.toBeInTheDocument()
rerender(
<DataSources searchText="searchable-provider" onSelect={vi.fn()} dataSources={[provider]} />,
)
expect(screen.getByRole('button', { name: 'Matched action' })).toBeInTheDocument()
expect(await screen.findByRole('button', { name: 'Matched action' })).toBeInTheDocument()
rerender(<DataSources searchText="" onSelect={vi.fn()} dataSources={[provider]} />)
await waitFor(() =>
expect(screen.queryByRole('button', { name: 'Matched action' })).not.toBeInTheDocument(),
@@ -158,7 +158,7 @@ it('filters by raw provider or action name and retains each matching provider gr
)
})
it('keeps flat letter navigation sorted with pinyin initials and nonalphabetic providers last', () => {
it('keeps flat letter navigation sorted with pinyin initials and nonalphabetic providers last', async () => {
const providers = [
'1Source',
'中文',
@@ -173,9 +173,9 @@ it('keeps flat letter navigation sorted with pinyin initials and nonalphabetic p
'Lima',
].map(namedProvider)
render(<DataSources searchText="" onSelect={vi.fn()} dataSources={providers} />)
const letters = screen
.getAllByRole('button')
.filter((button) => /^[A-Z#]$/.test(button.textContent ?? ''))
const letters = (await screen.findAllByRole('button')).filter((button) =>
/^[A-Z#]$/.test(button.textContent ?? ''),
)
expect(letters.map((button) => button.textContent)).toEqual([
'A',
'E',
@@ -216,7 +216,7 @@ it('keeps an expanded datasource open when the language changes its sort letter'
const { rerender } = render(
<DataSources searchText="" onSelect={vi.fn()} dataSources={[provider]} />,
)
await user.click(screen.getByRole('button', { name: 'File Source' }))
await user.click(await screen.findByRole('button', { name: 'File Source' }))
expect(screen.getByRole('button', { name: 'Local File' })).toBeInTheDocument()
language.value = 'zh_Hans'
@@ -483,13 +483,13 @@ describe('ToolPicker', () => {
expect(trigger).toHaveFocus()
})
it('should link the find-more footer to the marketplace tool category', () => {
it('should link the find-more footer to the marketplace tool category', async () => {
renderToolPicker({
isShow: true,
selectedTools: [],
})
const footerLink = screen.getByRole('link', { name: /plugin\.findMoreInMarketplace/i })
const footerLink = await screen.findByRole('link', { name: /plugin\.findMoreInMarketplace/i })
expect(footerLink.closest('footer')).toBeInTheDocument()
expect(footerLink).toHaveAttribute('href', 'https://marketplace.test/plugins/tool')
})
@@ -507,7 +507,7 @@ describe('ToolPicker', () => {
})
expect(screen.queryByText('Built-in Provider')).not.toBeInTheDocument()
expect(screen.getByText('Custom Provider')).toBeInTheDocument()
expect(await screen.findByText('Custom Provider')).toBeInTheDocument()
expect(screen.getByText('MCP Provider')).toBeInTheDocument()
await user.type(screen.getByRole('searchbox', { name: 'plugin.searchTools' }), 'weather')
@@ -612,7 +612,7 @@ describe('ToolPicker', () => {
renderToolPicker({ isShow: true, scope, selectedTools: [] })
expect(screen.getByText(visibleProvider)).toBeInTheDocument()
expect(await screen.findByText(visibleProvider)).toBeInTheDocument()
expect(screen.queryByText(hiddenProvider)).not.toBeInTheDocument()
expect(screen.queryByText('Built-in Provider')).not.toBeInTheDocument()
@@ -8,6 +8,7 @@ import type { ListRef } from '@/app/components/workflow/block-selector/marketpla
import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useDebounce } from 'ahooks'
import dynamic from 'next/dynamic'
import { useCallback, useMemo, useRef } from 'react'
import { trackEvent } from '@/app/components/base/amplitude'
import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/query'
@@ -18,7 +19,13 @@ import { renderI18nObject } from '@/i18n/metadata'
import { PluginCategoryEnum } from '../../plugins/types'
import { BlockEnum } from '../types'
import { DEFAULT_FILE_EXTENSIONS_IN_LOCAL_FILE_DATA_SOURCE } from './constants'
import { DatasourceList } from './datasource-list'
const DatasourceList = dynamic(
() => import('./datasource-list').then((module) => module.DatasourceList),
{
loading: () => <div className="h-24 animate-pulse rounded-lg bg-background-section" />,
},
)
type DataSourcesProps = {
className?: string
@@ -1,6 +1,7 @@
import type { RagPipelineDatasourceProviderResponse } from '@dify/contracts/api/console/rag/types.gen'
import type { OnSelectBlock, ToolWithProvider } from '../types'
import { useSuspenseQuery } from '@tanstack/react-query'
import dynamic from 'next/dynamic'
import { useEffect, useMemo } from 'react'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useFeaturedToolsRecommendations } from '@/service/use-plugins'
@@ -13,7 +14,10 @@ import {
} from '@/service/use-tools'
import { basePath } from '@/utils/var'
import { useWorkflowStore } from '../store'
import ToolBrowser from './tool-browser'
const ToolBrowser = dynamic(() => import('./tool-browser'), {
loading: () => <div className="h-24 animate-pulse rounded-lg bg-background-section" />,
})
function normalizeToolList(list: ToolWithProvider[] | undefined, currentBasePath?: string) {
if (!list || !currentBasePath) return list
@@ -8,12 +8,12 @@ import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverContent, PopoverTitle, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useBoolean } from 'ahooks'
import dynamic from 'next/dynamic'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import SearchBox from '@/app/components/plugins/marketplace/search-box'
import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
import { useCanManageTools } from '@/app/components/tools/hooks/use-tool-permissions'
import ToolBrowser from '@/app/components/workflow/block-selector/tool-browser'
import { toast } from '@/app/notifications'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { createCustomCollection } from '@/service/tools'
@@ -29,6 +29,10 @@ import {
useInvalidateAllWorkflowTools,
} from '@/service/use-tools'
const ToolBrowser = dynamic(() => import('./tool-browser'), {
loading: () => <div className="h-24 animate-pulse rounded-lg bg-background-section" />,
})
type Props = Readonly<
Pick<PopoverContentProps, 'placement' | 'sideOffset'> & {
disabled: boolean
@@ -31,7 +31,7 @@ vi.mock('../../hooks/use-workflow-mode', () => ({
useWorkflowMode: () => mockWorkflowMode,
}))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => {
dynamicMockState.calls += 1
return dynamicMockState.calls === 1 ? DynamicHeaderHistory : DynamicHeaderRestoring
+1 -1
View File
@@ -1,7 +1,7 @@
import type { HeaderInNormalProps } from './header-in-normal'
import type { HeaderInRestoringProps } from './header-in-restoring'
import type { HeaderInHistoryProps } from './header-in-view-history'
import dynamic from '@/next/dynamic'
import dynamic from 'next/dynamic'
import { useWorkflowMode } from '../hooks/use-workflow-mode'
import HeaderInNormal from './header-in-normal'
@@ -8,6 +8,7 @@ import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTitle, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
import dynamic from 'next/dynamic'
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SearchInput } from '@/app/components/base/search-input'
@@ -23,12 +24,16 @@ import { consoleQuery } from '@/service/console'
import { ViewType } from '../../../block-selector/types'
import ViewTypeSelect from '../../../block-selector/view-type-select'
import { useStrategyInfo } from '../../agent/use-config'
import { AgentStrategyList } from './agent-strategy-list'
import { InstallPluginButton } from './install-plugin-button'
import { SwitchPluginVersion } from './switch-plugin-version'
const DEFAULT_TAGS: ListProps['tags'] = []
const AgentStrategyList = dynamic(
() => import('./agent-strategy-list').then((module) => module.AgentStrategyList),
{ loading: () => <div className="h-24 animate-pulse rounded-lg bg-background-section" /> },
)
const NotFoundWarn = (props: { title: string; description: ReactNode }) => {
const { title, description } = props
const { t } = useTranslation(['workflow'])
@@ -103,7 +103,7 @@ vi.mock('@/app/components/workflow/panel/version-history-panel', () => ({
),
}))
vi.mock('@/next/dynamic', async () => {
vi.mock('next/dynamic', async () => {
const ReactModule = await import('react')
return {
+1 -1
View File
@@ -1,10 +1,10 @@
import type { FC } from 'react'
import type { VersionHistoryPanelProps } from '@/app/components/workflow/panel/version-history-panel'
import { cn } from '@langgenius/dify-ui/cn'
import dynamic from 'next/dynamic'
import { memo, useEffect, useRef } from 'react'
import { useStore as useReactflow } from 'reactflow'
import { useShallow } from 'zustand/react/shallow'
import dynamic from '@/next/dynamic'
import { Panel as NodePanel } from '../nodes'
import { useStore } from '../store'
import EnvPanel from './env-panel'
@@ -1,6 +1,6 @@
'use client'
import dynamic from 'next/dynamic'
import * as React from 'react'
import dynamic from '@/next/dynamic'
import { useWorkflowGeneratorStore } from './store'
// Lazy-load the modal so the bundle of the common layout stays light;
+1 -1
View File
@@ -1,13 +1,13 @@
'use client'
import { Separator } from '@langgenius/dify-ui/separator'
import { useSuspenseQuery } from '@tanstack/react-query'
import dynamic from 'next/dynamic'
import { useLocale } from '#i18n'
import { DifyLogo } from '@/app/components/base/logo/dify-logo'
import LocaleMenu from '@/app/signin/_locale-menu'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { setLocaleOnClient } from '@/i18n/client'
import { languages } from '@/i18n/language'
import dynamic from '@/next/dynamic'
const ThemeSelector = dynamic(() => import('@/app/components/base/theme-selector'), {
ssr: false,
@@ -12,7 +12,7 @@ const mockEducationStatus = vi.hoisted(() => ({
}))
const mockPricingModal = vi.hoisted(() => ({ isOpen: false }))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default:
() =>
({ expired, onClose }: { expired: boolean; onClose: () => void }) => (
+1 -1
View File
@@ -1,11 +1,11 @@
'use client'
import dynamic from 'next/dynamic'
import { useQueryState } from 'nuqs'
import {
pricingQueryParamName,
pricingQueryParser,
} from '@/app/components/billing/pricing/query-params'
import dynamic from '@/next/dynamic'
import { useEducationExpireNotice } from './use-expire-notice'
const ExpireNoticeModal = dynamic(() => import('./modal'), { ssr: false })
+1 -1
View File
@@ -7,7 +7,7 @@ vi.mock('@/i18n/client', () => ({
setLocaleOnClient: vi.fn(),
}))
vi.mock('@/next/dynamic', () => ({
vi.mock('next/dynamic', () => ({
default: () => () => null,
}))
+1 -1
View File
@@ -1,12 +1,12 @@
'use client'
import { Separator } from '@langgenius/dify-ui/separator'
import { useSuspenseQuery } from '@tanstack/react-query'
import dynamic from 'next/dynamic'
import { useLocale } from '#i18n'
import { DifyLogo } from '@/app/components/base/logo/dify-logo'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { setLocaleOnClient } from '@/i18n/client'
import { languages } from '@/i18n/language'
import dynamic from '@/next/dynamic'
import LocaleMenu from './_locale-menu'
const ThemeSelector = dynamic(() => import('@/app/components/base/theme-selector'), {
+1 -1
View File
@@ -8,9 +8,9 @@ import type { UpdatePluginPayload } from '@/app/components/plugins/types'
import type { InputVar } from '@/app/components/workflow/types'
import type { ExternalDataTool } from '@/models/common'
import type { ModerationConfig, PromptVariable } from '@/models/debug'
import dynamic from 'next/dynamic'
import { useCallback, useState } from 'react'
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import dynamic from '@/next/dynamic'
import { useTriggerEventsLimitModal } from './hooks/use-trigger-events-limit-modal'
import { ModalContext } from './modal-context'
@@ -6,6 +6,7 @@ import type { ConfigParams, SettingsAppInfo } from '@/app/components/app/overvie
import type { AppIconType } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import dynamic from 'next/dynamic'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
@@ -20,7 +21,6 @@ import { AccessPointUrl } from '@/app/components/base/access-point/url'
import AppIcon from '@/app/components/base/app-icon'
import { toast } from '@/app/notifications'
import { getAgentACLCapabilities } from '@/features/agent-v2/acl'
import dynamic from '@/next/dynamic'
import { consoleQuery } from '@/service/console'
import { AppModeEnum } from '@/types/app'
import { getAgentWebAppUrl } from '../../web-app-access'
@@ -51,7 +51,7 @@ vi.mock('@/features/system-features/state', async () => {
return createSystemFeaturesStateModuleMock(() => mockConsoleState)
})
vi.mock('@/next/dynamic', async () => {
vi.mock('next/dynamic', async () => {
const { useState } = await import('react')
return {
@@ -13,6 +13,7 @@ import type { Inputs } from '@/models/debug'
import { Avatar } from '@langgenius/dify-ui/avatar'
import { cn } from '@langgenius/dify-ui/cn'
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
import dynamic from 'next/dynamic'
import { useCallback, useImperativeHandle, useLayoutEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AgentRosterResponseContent } from '@/app/components/base/chat/chat/answer/agent-roster-response-content'
@@ -21,7 +22,6 @@ import { getLastAnswer, isValidGeneratedAnswer } from '@/app/components/base/cha
import { toast } from '@/app/notifications'
import { useDocLink } from '@/context/i18n'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import dynamic from '@/next/dynamic'
import { consoleClient, consoleQuery } from '@/service/console'
import { getAgentSoulInputs, getAgentSoulInputsForm } from './chat-config'
@@ -31,7 +31,7 @@ const mocks = vi.hoisted(() => ({
})),
}))
vi.mock('echarts-for-react', () => ({
vi.mock('echarts-for-react/esm/core', () => ({
default: ({ option, style }: { option: EChartsOption; style?: React.CSSProperties }) => {
mocks.chartOptions.push(option)
@@ -3,9 +3,10 @@
import type { AgentMonitoringChartRow, AgentMonitoringChartType } from './chart-utils'
import type { I18nKeysWithPrefix } from '@/types/i18n'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import ReactECharts from 'echarts-for-react'
import ReactECharts from 'echarts-for-react/esm/core'
import { useId } from 'react'
import { useTranslation } from 'react-i18next'
import { echarts } from '@/app/components/base/line-chart/echarts'
import { buildChartOptions, getChartValueField, getTokenSummary } from './chart-utils'
type AgentMonitoringChartProps = {
@@ -84,7 +85,7 @@ export function AgentMonitoringChart({
</div>
<div className="h-60 px-6">
<ReactECharts option={options} style={{ height: 240, width: '100%' }} />
<ReactECharts echarts={echarts} option={options} style={{ height: 240, width: '100%' }} />
</div>
</article>
)
@@ -7,6 +7,7 @@ import type { TrackCreateAppParams } from '@/utils/create-app-tracking'
import { cn } from '@langgenius/dify-ui/cn'
import { noop, useQueryClient, useSuspenseQueries, useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai'
import dynamic from 'next/dynamic'
import { useQueryState } from 'nuqs'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@@ -30,7 +31,6 @@ import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useImportDSL } from '@/hooks/use-import-dsl'
import { DSLImportMode } from '@/models/app'
import dynamic from '@/next/dynamic'
import { consoleQuery } from '@/service/console'
import { trackCreateApp } from '@/utils/create-app-tracking'
import { hasPermission } from '@/utils/permission'
@@ -2,8 +2,8 @@
import type { RecentAppResponse } from '@dify/contracts/api/console/apps/types.gen'
import type { RecommendedAppResponse } from '@dify/contracts/api/console/explore/types.gen'
import dynamic from 'next/dynamic'
import { STEP_BY_STEP_TOUR_TARGETS } from '@/app/components/step-by-step-tour/target-registry'
import dynamic from '@/next/dynamic'
import { ContinueWork } from '../continue-work/continue-work'
const LearnDify = dynamic(() => import('@/app/components/explore/learn-dify'), { ssr: false })
+1 -1
View File
@@ -11,13 +11,13 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { noop } from 'es-toolkit/function'
import dynamic from 'next/dynamic'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SkeletonRectangle } from '@/app/components/base/skeleton'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { toast } from '@/app/notifications'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import dynamic from '@/next/dynamic'
import { consoleQuery } from '@/service/console'
import { downloadBlob } from '@/utils/download'
import { fetchSkillFileBlob } from '../client'
@@ -1,6 +1,6 @@
'use client'
import dynamic from '@/next/dynamic'
import dynamic from 'next/dynamic'
const TagManagementModal = dynamic(
() =>
-1
View File
@@ -1 +0,0 @@
export { default } from 'next/dynamic'