mirror of
https://github.com/langgenius/dify.git
synced 2026-07-21 18:58:35 +08:00
perf(web): improve vinext home startup time (#38219)
This commit is contained in:
parent
200f8b800f
commit
b1bb6ef977
21
web/app/(commonLayout)/global-mounts.tsx
Normal file
21
web/app/(commonLayout)/global-mounts.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import dynamic from '@/next/dynamic'
|
||||
|
||||
const InSiteMessageNotification = dynamic(() => import('@/app/components/app/in-site-message/notification'), { ssr: false })
|
||||
const PartnerStack = dynamic(() => import('@/app/components/billing/partner-stack'), { ssr: false })
|
||||
const ReadmePanel = dynamic(() => import('@/app/components/plugins/readme-panel'), { ssr: false })
|
||||
const WorkflowGeneratorMount = dynamic(() => import('@/app/components/workflow/workflow-generator/mount'), { ssr: false })
|
||||
const GotoAnything = dynamic(() => import('@/app/components/goto-anything').then(mod => mod.GotoAnything), { ssr: false })
|
||||
|
||||
export function CommonLayoutGlobalMounts() {
|
||||
return (
|
||||
<>
|
||||
<InSiteMessageNotification />
|
||||
<PartnerStack />
|
||||
<ReadmePanel />
|
||||
<GotoAnything />
|
||||
<WorkflowGeneratorMount />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -1,20 +1,16 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import InSiteMessageNotification from '@/app/components/app/in-site-message/notification'
|
||||
import AmplitudeProvider from '@/app/components/base/amplitude'
|
||||
import { GoogleAnalyticsScripts } from '@/app/components/base/ga'
|
||||
import Zendesk from '@/app/components/base/zendesk'
|
||||
import { EducationVerifyActionRecorder } from '@/app/components/education-verify-action-recorder'
|
||||
import { GotoAnything } from '@/app/components/goto-anything'
|
||||
import MainNavLayout from '@/app/components/main-nav/layout'
|
||||
import { NextRouteStateBridge } from '@/app/components/next-route-state'
|
||||
import { OAuthRegistrationAnalytics } from '@/app/components/oauth-registration-analytics'
|
||||
import ReadmePanel from '@/app/components/plugins/readme-panel'
|
||||
import WorkflowGeneratorMount from '@/app/components/workflow/workflow-generator/mount'
|
||||
import { AppContextProvider } from '@/context/app-context-provider'
|
||||
import { EventEmitterContextProvider } from '@/context/event-emitter-provider'
|
||||
import { ModalContextProvider } from '@/context/modal-context-provider'
|
||||
import { ProviderContextProvider } from '@/context/provider-context-provider'
|
||||
import PartnerStack from '../components/billing/partner-stack'
|
||||
import { CommonLayoutGlobalMounts } from './global-mounts'
|
||||
import { CommonLayoutHydrationBoundary } from './hydration-boundary'
|
||||
|
||||
export default async function Layout({ children }: { children: ReactNode }) {
|
||||
@ -33,11 +29,7 @@ export default async function Layout({ children }: { children: ReactNode }) {
|
||||
<MainNavLayout>
|
||||
{children}
|
||||
</MainNavLayout>
|
||||
<InSiteMessageNotification />
|
||||
<PartnerStack />
|
||||
<ReadmePanel />
|
||||
<GotoAnything />
|
||||
<WorkflowGeneratorMount />
|
||||
<CommonLayoutGlobalMounts />
|
||||
</ModalContextProvider>
|
||||
</ProviderContextProvider>
|
||||
</EventEmitterContextProvider>
|
||||
|
||||
@ -495,7 +495,7 @@ describe('MainNav', () => {
|
||||
expect(screen.queryByRole('link', { name: /common.menus.deployments/ })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('aligns the global navigation spacing with the main sidebar design', () => {
|
||||
it('aligns the global navigation spacing with the main sidebar design', async () => {
|
||||
mockInstalledApps = [createInstalledApp()]
|
||||
|
||||
renderMainNav()
|
||||
@ -508,7 +508,7 @@ describe('MainNav', () => {
|
||||
expect(homeLink.closest('nav')).toHaveClass('isolate', 'flex', 'flex-col', 'gap-px', 'p-2')
|
||||
expect(homeLink).toHaveClass('h-8', 'w-full', 'rounded-[10px]', 'px-2', 'py-1.5')
|
||||
|
||||
const webAppsButton = screen.getByRole('button', { name: 'explore.sidebar.webApps' })
|
||||
const webAppsButton = await screen.findByRole('button', { name: 'explore.sidebar.webApps' })
|
||||
expect(webAppsButton.parentElement).toHaveClass('py-1', 'pr-2', 'pl-2')
|
||||
|
||||
const helpButton = screen.getByRole('button', { name: 'common.mainNav.help.openMenu' })
|
||||
@ -551,7 +551,7 @@ describe('MainNav', () => {
|
||||
expect(container.querySelector('.relative.z-30')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('hides the environment tag when app detail navigation is collapsed', () => {
|
||||
it('hides the environment tag when app detail navigation is collapsed', async () => {
|
||||
mockPathname = '/app/app-1/overview'
|
||||
;(useAppContext as Mock).mockReturnValue({
|
||||
...appContextValue,
|
||||
@ -562,7 +562,7 @@ describe('MainNav', () => {
|
||||
})
|
||||
|
||||
const { container } = renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('app-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('app-detail-toggle'))
|
||||
|
||||
expect(screen.queryByText('common.environment.testing')).not.toBeInTheDocument()
|
||||
expect(container.querySelector('.relative.z-30')).not.toBeInTheDocument()
|
||||
@ -659,7 +659,7 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('link', { name: /common.mainNav.home/ })).not.toHaveAttribute('aria-current')
|
||||
})
|
||||
|
||||
it('replaces global navigation with snippet detail navigation on snippet routes', () => {
|
||||
it('replaces global navigation with snippet detail navigation on snippet routes', async () => {
|
||||
mockPathname = '/snippets/snippet-1/orchestrate'
|
||||
snippetDraftState.inputFields = snippetFields
|
||||
snippetNavigationState.onFieldsChange = mockSnippetFieldsChange
|
||||
@ -671,8 +671,8 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-62')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('bg-background-body')
|
||||
expect(screen.getByTestId('snippet-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('snippet-sidebar-content')).toHaveAttribute('data-readonly', 'false')
|
||||
expect(await screen.findByTestId('snippet-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(await screen.findByTestId('snippet-sidebar-content')).toHaveAttribute('data-readonly', 'false')
|
||||
expect(screen.getByText(snippet.name)).toBeInTheDocument()
|
||||
expect(screen.getByText('query')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'change snippet fields' }))
|
||||
@ -686,14 +686,14 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('button', { name: 'common.mainNav.help.openMenu' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('collapses snippet detail navigation from the top-right toggle', () => {
|
||||
it('collapses snippet detail navigation from the top-right toggle', async () => {
|
||||
mockPathname = '/snippets/snippet-1/orchestrate'
|
||||
snippetDraftState.inputFields = snippetFields
|
||||
snippetNavigationState.onFieldsChange = mockSnippetFieldsChange
|
||||
snippetNavigationState.snippet = snippet
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('snippet-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('snippet-detail-toggle'))
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
@ -704,13 +704,13 @@ describe('MainNav', () => {
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
it('replaces global navigation with app detail navigation on app routes', () => {
|
||||
it('replaces global navigation with app detail navigation on app routes', async () => {
|
||||
mockPathname = '/app/app-1/overview'
|
||||
|
||||
renderMainNav()
|
||||
|
||||
expect(screen.getByTestId('app-detail-top')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('app-detail-section')).toBeInTheDocument()
|
||||
expect(await screen.findByTestId('app-detail-top')).toBeInTheDocument()
|
||||
expect(await screen.findByTestId('app-detail-section')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('app-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('app-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-62')
|
||||
@ -742,56 +742,56 @@ describe('MainNav', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('collapses app detail navigation from the top-right toggle', () => {
|
||||
it('collapses app detail navigation from the top-right toggle', async () => {
|
||||
mockPathname = '/app/app-1/overview'
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('app-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('app-detail-toggle'))
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16')
|
||||
expect(screen.getByRole('complementary')).not.toHaveClass('transition-none')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByTestId('app-detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
expect(screen.getByTestId('app-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(await screen.findByTestId('app-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
it('shows app detail navigation as a floating preview when hovering the collapsed top toggle', () => {
|
||||
it('shows app detail navigation as a floating preview when hovering the collapsed top toggle', async () => {
|
||||
mockPathname = '/app/app-1/overview'
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('app-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('app-detail-toggle'))
|
||||
fireEvent.mouseEnter(screen.getByTestId('app-detail-top').parentElement!)
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16', 'overflow-visible')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
expect(screen.getAllByTestId('app-detail-top')).toHaveLength(1)
|
||||
expect(screen.getByTestId('app-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('app-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(await screen.findByTestId('app-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
})
|
||||
|
||||
it('persists expanded app detail navigation without width animation when clicking the hovered toggle', () => {
|
||||
it('persists expanded app detail navigation without width animation when clicking the hovered toggle', async () => {
|
||||
mockPathname = '/app/app-1/overview'
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('app-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('app-detail-toggle'))
|
||||
fireEvent.mouseEnter(screen.getByTestId('app-detail-top').parentElement!)
|
||||
fireEvent.click(screen.getByTestId('app-detail-toggle'))
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-62', 'transition-none')
|
||||
expect(screen.getByRole('complementary')).not.toHaveClass('overflow-visible')
|
||||
expect(screen.getByTestId('app-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('app-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(await screen.findByTestId('app-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('expand')
|
||||
})
|
||||
|
||||
it('replaces global navigation with dataset detail navigation on dataset routes', () => {
|
||||
it('replaces global navigation with dataset detail navigation on dataset routes', async () => {
|
||||
mockPathname = '/datasets/dataset-1/documents'
|
||||
|
||||
renderMainNav()
|
||||
|
||||
expect(screen.getByTestId('dataset-detail-top')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('dataset-detail-section')).toBeInTheDocument()
|
||||
expect(await screen.findByTestId('dataset-detail-top')).toBeInTheDocument()
|
||||
expect(await screen.findByTestId('dataset-detail-section')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('dataset-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('dataset-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-62')
|
||||
@ -802,42 +802,44 @@ describe('MainNav', () => {
|
||||
expect(screen.queryByRole('link', { name: /common.menus.datasets/ })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('collapses dataset detail navigation from the top-right toggle', () => {
|
||||
it('collapses dataset detail navigation from the top-right toggle', async () => {
|
||||
mockPathname = '/datasets/dataset-1/documents'
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('dataset-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('dataset-detail-toggle'))
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByTestId('dataset-detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
expect(screen.getByTestId('dataset-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(await screen.findByTestId('dataset-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
it('shows dataset detail navigation as a floating preview when hovering the collapsed top toggle', () => {
|
||||
it('shows dataset detail navigation as a floating preview when hovering the collapsed top toggle', async () => {
|
||||
mockPathname = '/datasets/dataset-1/documents'
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('dataset-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('dataset-detail-toggle'))
|
||||
fireEvent.mouseEnter(screen.getByTestId('dataset-detail-top').parentElement!)
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16', 'overflow-visible')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
expect(screen.getAllByTestId('dataset-detail-top')).toHaveLength(1)
|
||||
expect(screen.getByTestId('dataset-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('dataset-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(await screen.findByTestId('dataset-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
})
|
||||
|
||||
it('replaces global navigation with agent detail navigation on roster detail routes', () => {
|
||||
it('replaces global navigation with agent detail navigation on roster detail routes', async () => {
|
||||
mockPathname = '/roster/agent/agent-1/configure'
|
||||
|
||||
renderMainNav()
|
||||
|
||||
expect(screen.getByTestId('agent-detail-top')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('agent-detail-section')).toBeInTheDocument()
|
||||
expect(await screen.findByTestId('agent-detail-top')).toBeInTheDocument()
|
||||
const agentDetailNavigation = await screen.findByRole('navigation', { name: 'agentV2.agentDetail.navigationLabel' })
|
||||
expect(agentDetailNavigation).toBeInTheDocument()
|
||||
expect(screen.getByRole('link', { name: /agentV2.agentDetail.sections.configure/ })).toHaveAttribute('href', '/roster/agent/agent-1/configure')
|
||||
expect(screen.getByTestId('agent-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('agent-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(agentDetailNavigation).toHaveClass('px-1')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-62')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('bg-background-body')
|
||||
@ -873,16 +875,16 @@ describe('MainNav', () => {
|
||||
expect(screen.queryByRole('link', { name: /common.menus.deployments/ })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('collapses agent detail navigation from the top-right toggle', () => {
|
||||
it('collapses agent detail navigation from the top-right toggle', async () => {
|
||||
mockPathname = '/roster/agent/agent-1/configure'
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('agent-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('agent-detail-toggle'))
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16')
|
||||
expect(screen.getByRole('complementary')).toHaveClass('p-1')
|
||||
expect(screen.getByTestId('agent-detail-top')).toHaveAttribute('data-expand', 'false')
|
||||
expect(screen.getByTestId('agent-detail-section')).toHaveAttribute('data-expand', 'false')
|
||||
expect(await screen.findByRole('navigation', { name: 'agentV2.agentDetail.navigationLabel' })).toHaveClass('px-3')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
})
|
||||
|
||||
@ -923,18 +925,18 @@ describe('MainNav', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('shows agent detail navigation as a floating preview when hovering the collapsed top toggle', () => {
|
||||
it('shows agent detail navigation as a floating preview when hovering the collapsed top toggle', async () => {
|
||||
mockPathname = '/roster/agent/agent-1/configure'
|
||||
|
||||
renderMainNav()
|
||||
fireEvent.click(screen.getByTestId('agent-detail-toggle'))
|
||||
fireEvent.click(await screen.findByTestId('agent-detail-toggle'))
|
||||
fireEvent.mouseEnter(screen.getByTestId('agent-detail-top').parentElement!)
|
||||
|
||||
expect(screen.getByRole('complementary')).toHaveClass('w-16', 'overflow-visible')
|
||||
expect(localStorage.getItem(DETAIL_SIDEBAR_STORAGE_KEY)).toBe('collapse')
|
||||
expect(screen.getAllByTestId('agent-detail-top')).toHaveLength(1)
|
||||
expect(screen.getByTestId('agent-detail-top')).toHaveAttribute('data-expand', 'true')
|
||||
expect(screen.getByTestId('agent-detail-section')).toHaveAttribute('data-expand', 'true')
|
||||
expect(await screen.findByRole('navigation', { name: 'agentV2.agentDetail.navigationLabel' })).toHaveClass('px-1')
|
||||
})
|
||||
|
||||
it.each([
|
||||
@ -1258,12 +1260,12 @@ describe('MainNav', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('collapses and expands installed web apps from the section arrow', () => {
|
||||
it('collapses and expands installed web apps from the section arrow', async () => {
|
||||
mockInstalledApps = [createInstalledApp()]
|
||||
|
||||
renderMainNav()
|
||||
|
||||
const webAppsButton = screen.getByRole('button', { name: 'explore.sidebar.webApps' })
|
||||
const webAppsButton = await screen.findByRole('button', { name: 'explore.sidebar.webApps' })
|
||||
expect(webAppsButton).toHaveAttribute('aria-expanded', 'true')
|
||||
expect(screen.getByText('Alpha App')).toBeInTheDocument()
|
||||
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
'use client'
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { SnippetCollapsedPreview } from '@/app/components/snippets/components/snippet-collapsed-preview'
|
||||
import { SnippetSidebarContent } from '@/app/components/snippets/components/snippet-sidebar'
|
||||
import { useSnippetDraftStore } from '@/app/components/snippets/draft-store'
|
||||
import { useSnippetDetailStore } from '@/app/components/snippets/store'
|
||||
|
||||
type SnippetDetailSectionProps = {
|
||||
expand: boolean
|
||||
}
|
||||
|
||||
export function SnippetDetailSection({ expand }: SnippetDetailSectionProps) {
|
||||
const snippetNavigation = useSnippetDetailStore(useShallow(state => ({
|
||||
onFieldsChange: state.onFieldsChange,
|
||||
readonly: state.readonly,
|
||||
snippet: state.snippet,
|
||||
})))
|
||||
const snippetInputFields = useSnippetDraftStore(state => state.inputFields)
|
||||
|
||||
if (!expand)
|
||||
return <SnippetCollapsedPreview inputFieldCount={snippetInputFields.length} />
|
||||
|
||||
if (!snippetNavigation.snippet || !snippetNavigation.onFieldsChange)
|
||||
return null
|
||||
|
||||
return (
|
||||
<SnippetSidebarContent
|
||||
snippet={snippetNavigation.snippet}
|
||||
fields={snippetInputFields}
|
||||
readonly={snippetNavigation.readonly}
|
||||
onFieldsChange={snippetNavigation.onFieldsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -7,31 +7,21 @@ import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import AppDetailSection from '@/app/components/app-sidebar/app-detail-section'
|
||||
import AppDetailTop from '@/app/components/app-sidebar/app-detail-top'
|
||||
import DatasetDetailSection from '@/app/components/app-sidebar/dataset-detail-section'
|
||||
import DatasetDetailTop from '@/app/components/app-sidebar/dataset-detail-top'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import EnvNav from '@/app/components/header/env-nav'
|
||||
import { SnippetCollapsedPreview } from '@/app/components/snippets/components/snippet-collapsed-preview'
|
||||
import { SnippetSidebarContent } from '@/app/components/snippets/components/snippet-sidebar'
|
||||
import { useSnippetDraftStore } from '@/app/components/snippets/draft-store'
|
||||
import { useSnippetDetailStore } from '@/app/components/snippets/store'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { AgentDetailSection, AgentDetailTop } from '@/features/agent-v2/agent-detail/navigation'
|
||||
import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag'
|
||||
import { DeploymentDetailSection, DeploymentDetailTop } from '@/features/deployments/detail/deployment-sidebar'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import Link from '@/next/link'
|
||||
import { usePathname } from '@/next/navigation'
|
||||
import AccountSection from './components/account-section'
|
||||
import HelpMenu from './components/help-menu'
|
||||
import MainNavLink from './components/nav-link'
|
||||
import { MainNavSearchButton } from './components/search-button'
|
||||
import SnippetDetailTop from './components/snippet-detail-top'
|
||||
import WebAppsSection from './components/web-apps-section'
|
||||
import { WorkspaceCard } from './components/workspace-card'
|
||||
import { isMainNavRouteVisible, MAIN_NAV_ROUTES } from './routes'
|
||||
import { useDetailSidebarMode } from './storage'
|
||||
@ -41,6 +31,16 @@ const DATASET_DOCUMENT_CREATION_ROUTES = new Set(['create', 'create-from-pipelin
|
||||
const DEPLOYMENT_COLLECTION_ROUTES = new Set(['create'])
|
||||
const secondarySidebarHelpTriggerIcon = <span aria-hidden className="i-ri-question-line size-4 shrink-0" />
|
||||
|
||||
const AppDetailSection = dynamic(() => import('@/app/components/app-sidebar/app-detail-section'), { ssr: false })
|
||||
const AppDetailTop = dynamic(() => import('@/app/components/app-sidebar/app-detail-top'), { ssr: false })
|
||||
const DatasetDetailSection = dynamic(() => import('@/app/components/app-sidebar/dataset-detail-section'), { ssr: false })
|
||||
const DatasetDetailTop = dynamic(() => import('@/app/components/app-sidebar/dataset-detail-top'), { ssr: false })
|
||||
const AgentDetailSection = dynamic(() => import('@/features/agent-v2/agent-detail/navigation').then(mod => mod.AgentDetailSection), { ssr: false })
|
||||
const AgentDetailTop = dynamic(() => import('@/features/agent-v2/agent-detail/navigation').then(mod => mod.AgentDetailTop), { ssr: false })
|
||||
const SnippetDetailTop = dynamic(() => import('./components/snippet-detail-top'), { ssr: false })
|
||||
const SnippetDetailSection = dynamic(() => import('./components/snippet-detail-section').then(mod => mod.SnippetDetailSection), { ssr: false })
|
||||
const WebAppsSection = dynamic(() => import('./components/web-apps-section'), { ssr: false })
|
||||
|
||||
function SecondarySidebarHelpMenu({
|
||||
triggerClassName,
|
||||
}: {
|
||||
@ -103,12 +103,6 @@ export function MainNav({
|
||||
const showDeploymentDetailNavigation = canUseAppDeploy && !isCurrentWorkspaceDatasetOperator && isDeploymentDetailPathname(pathname)
|
||||
const showSnippetDetailNavigation = isSnippetDetailPathname(pathname)
|
||||
const showDetailNavigation = showAppDetailNavigation || showDatasetDetailNavigation || showAgentDetailNavigation || showDeploymentDetailNavigation || showSnippetDetailNavigation
|
||||
const snippetNavigation = useSnippetDetailStore(useShallow(state => ({
|
||||
onFieldsChange: state.onFieldsChange,
|
||||
readonly: state.readonly,
|
||||
snippet: state.snippet,
|
||||
})))
|
||||
const snippetInputFields = useSnippetDraftStore(state => state.inputFields)
|
||||
const { hasAppDetail, setAppDetail } = useAppStore(useShallow(state => ({
|
||||
hasAppDetail: !!state.appDetail,
|
||||
setAppDetail: state.setAppDetail,
|
||||
@ -307,18 +301,7 @@ export function MainNav({
|
||||
? <AgentDetailSection expand={detailNavigationVisibleExpanded} />
|
||||
: showDeploymentDetailNavigation
|
||||
? <DeploymentDetailSection expand={detailNavigationVisibleExpanded} />
|
||||
: detailNavigationVisibleExpanded
|
||||
? snippetNavigation.snippet && snippetNavigation.onFieldsChange
|
||||
? (
|
||||
<SnippetSidebarContent
|
||||
snippet={snippetNavigation.snippet}
|
||||
fields={snippetInputFields}
|
||||
readonly={snippetNavigation.readonly}
|
||||
onFieldsChange={snippetNavigation.onFieldsChange}
|
||||
/>
|
||||
)
|
||||
: null
|
||||
: <SnippetCollapsedPreview inputFieldCount={snippetInputFields.length} />
|
||||
: <SnippetDetailSection expand={detailNavigationVisibleExpanded} />
|
||||
: (
|
||||
<>
|
||||
<nav className="isolate flex flex-col gap-px p-2">
|
||||
|
||||
@ -2,10 +2,10 @@
|
||||
import type { Resource } from 'i18next'
|
||||
import type { Locale } from '.'
|
||||
import type { Namespace, NamespaceInFileName } from './resources'
|
||||
import { kebabCase } from 'es-toolkit/string'
|
||||
import { createInstance } from 'i18next'
|
||||
import resourcesToBackend from 'i18next-resources-to-backend'
|
||||
import { getI18n, initReactI18next } from 'react-i18next'
|
||||
import { loadI18nResource } from './load-resource'
|
||||
import { getInitOptions } from './settings'
|
||||
|
||||
export function createI18nextInstance(lng: Locale, resources: Resource) {
|
||||
@ -15,10 +15,7 @@ export function createI18nextInstance(lng: Locale, resources: Resource) {
|
||||
.use(resourcesToBackend((
|
||||
language: Locale,
|
||||
namespace: NamespaceInFileName | Namespace,
|
||||
) => {
|
||||
const namespaceKebab = kebabCase(namespace)
|
||||
return import(`../i18n/${language}/${namespaceKebab}.json`)
|
||||
}))
|
||||
) => loadI18nResource(language, namespace)))
|
||||
.init({
|
||||
...getInitOptions(),
|
||||
lng,
|
||||
|
||||
35
web/i18n-config/load-resource.ts
Normal file
35
web/i18n-config/load-resource.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import type { ResourceKey } from 'i18next'
|
||||
import type { Locale } from './language'
|
||||
import type { Namespace, NamespaceInFileName } from './resources'
|
||||
import { kebabCase } from 'es-toolkit/string'
|
||||
import { LanguagesSupported } from './language'
|
||||
|
||||
type LocaleResourceModule = {
|
||||
loadResource: (fileNamespace: string) => Promise<{ default: ResourceKey }>
|
||||
}
|
||||
|
||||
const legacyLocaleMap: Partial<Record<Locale, Locale>> = {
|
||||
en_US: 'en-US',
|
||||
ja_JP: 'ja-JP',
|
||||
zh_Hans: 'zh-Hans',
|
||||
}
|
||||
|
||||
const defaultLocale = 'en-US' satisfies Locale
|
||||
|
||||
const normalizeLocale = (locale: Locale): Locale => {
|
||||
const normalized = legacyLocaleMap[locale] ?? locale
|
||||
if (LanguagesSupported.includes(normalized))
|
||||
return normalized
|
||||
|
||||
return defaultLocale
|
||||
}
|
||||
|
||||
const loadLocaleResources = (locale: Locale): Promise<LocaleResourceModule> => {
|
||||
const normalized = normalizeLocale(locale)
|
||||
return import(`./locale-resources/${normalized}.ts`)
|
||||
}
|
||||
|
||||
export const loadI18nResource = async (locale: Locale, namespace: Namespace | NamespaceInFileName) => {
|
||||
const { loadResource } = await loadLocaleResources(locale)
|
||||
return loadResource(kebabCase(namespace))
|
||||
}
|
||||
1
web/i18n-config/locale-resources/ar-TN.ts
Normal file
1
web/i18n-config/locale-resources/ar-TN.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/ar-TN/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/de-DE.ts
Normal file
1
web/i18n-config/locale-resources/de-DE.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/de-DE/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/en-US.ts
Normal file
1
web/i18n-config/locale-resources/en-US.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/en-US/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/es-ES.ts
Normal file
1
web/i18n-config/locale-resources/es-ES.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/es-ES/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/fa-IR.ts
Normal file
1
web/i18n-config/locale-resources/fa-IR.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/fa-IR/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/fr-FR.ts
Normal file
1
web/i18n-config/locale-resources/fr-FR.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/fr-FR/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/hi-IN.ts
Normal file
1
web/i18n-config/locale-resources/hi-IN.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/hi-IN/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/id-ID.ts
Normal file
1
web/i18n-config/locale-resources/id-ID.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/id-ID/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/it-IT.ts
Normal file
1
web/i18n-config/locale-resources/it-IT.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/it-IT/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/ja-JP.ts
Normal file
1
web/i18n-config/locale-resources/ja-JP.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/ja-JP/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/ko-KR.ts
Normal file
1
web/i18n-config/locale-resources/ko-KR.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/ko-KR/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/nl-NL.ts
Normal file
1
web/i18n-config/locale-resources/nl-NL.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/nl-NL/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/pl-PL.ts
Normal file
1
web/i18n-config/locale-resources/pl-PL.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/pl-PL/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/pt-BR.ts
Normal file
1
web/i18n-config/locale-resources/pt-BR.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/pt-BR/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/ro-RO.ts
Normal file
1
web/i18n-config/locale-resources/ro-RO.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/ro-RO/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/ru-RU.ts
Normal file
1
web/i18n-config/locale-resources/ru-RU.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/ru-RU/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/sl-SI.ts
Normal file
1
web/i18n-config/locale-resources/sl-SI.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/sl-SI/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/th-TH.ts
Normal file
1
web/i18n-config/locale-resources/th-TH.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/th-TH/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/tr-TR.ts
Normal file
1
web/i18n-config/locale-resources/tr-TR.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/tr-TR/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/uk-UA.ts
Normal file
1
web/i18n-config/locale-resources/uk-UA.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/uk-UA/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/vi-VN.ts
Normal file
1
web/i18n-config/locale-resources/vi-VN.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/vi-VN/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/zh-Hans.ts
Normal file
1
web/i18n-config/locale-resources/zh-Hans.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/zh-Hans/${fileNamespace}.json`)
|
||||
1
web/i18n-config/locale-resources/zh-Hant.ts
Normal file
1
web/i18n-config/locale-resources/zh-Hant.ts
Normal file
@ -0,0 +1 @@
|
||||
export const loadResource = (fileNamespace: string) => import(`../../i18n/zh-Hant/${fileNamespace}.json`)
|
||||
@ -2,7 +2,6 @@ import type { i18n as I18nInstance, Resource, ResourceLanguage } from 'i18next'
|
||||
import type { Locale } from '.'
|
||||
import type { Namespace, NamespaceInFileName } from './resources'
|
||||
import { match } from '@formatjs/intl-localematcher'
|
||||
import { kebabCase } from 'es-toolkit/compat'
|
||||
import { camelCase } from 'es-toolkit/string'
|
||||
import { createInstance } from 'i18next'
|
||||
import resourcesToBackend from 'i18next-resources-to-backend'
|
||||
@ -12,6 +11,7 @@ import { initReactI18next } from 'react-i18next/initReactI18next'
|
||||
import { cookies, headers } from '@/next/headers'
|
||||
import { serverOnlyContext } from '@/utils/server-only-context'
|
||||
import { i18n } from '.'
|
||||
import { loadI18nResource } from './load-resource'
|
||||
import { namespacesInFileName } from './resources'
|
||||
import { getInitOptions } from './settings'
|
||||
|
||||
@ -26,10 +26,7 @@ const getOrCreateI18next = async (lng: Locale) => {
|
||||
instance = createInstance()
|
||||
await instance
|
||||
.use(initReactI18next)
|
||||
.use(resourcesToBackend((language: Locale, namespace: Namespace | NamespaceInFileName) => {
|
||||
const fileNamespace = kebabCase(namespace)
|
||||
return import(`../i18n/${language}/${fileNamespace}.json`)
|
||||
}))
|
||||
.use(resourcesToBackend((language: Locale, namespace: Namespace | NamespaceInFileName) => loadI18nResource(language, namespace)))
|
||||
.init({
|
||||
...getInitOptions(),
|
||||
lng,
|
||||
@ -85,7 +82,7 @@ export const getResources = cache(async (lng: Locale): Promise<Resource> => {
|
||||
|
||||
await Promise.all(
|
||||
(namespacesInFileName).map(async (ns) => {
|
||||
const mod = await import(`../i18n/${lng}/${ns}.json`)
|
||||
const mod = await loadI18nResource(lng, ns)
|
||||
messages[camelCase(ns)] = mod.default
|
||||
}),
|
||||
)
|
||||
|
||||
@ -35,6 +35,7 @@ const config: KnipConfig = {
|
||||
ignoreFiles: [
|
||||
'features/agent-v2/agent-detail/configure/components/orchestrate/memory.tsx',
|
||||
'features/agent-v2/agent-detail/configure/components/orchestrate/prompt-editor/option-menu.tsx',
|
||||
'i18n-config/locale-resources/*.ts',
|
||||
],
|
||||
ignoreBinaries: [
|
||||
'only-allow',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user