From 8fc2807194ba1ed1e068808eb3a7eceead25fa9b Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 1 Jun 2026 18:15:25 +0800 Subject: [PATCH] feat(web): create system-features vertical (#36894) --- .../plugin-marketplace-to-install.test.tsx | 2 +- web/__tests__/utils/mock-system-features.tsx | 32 +++-- .../__tests__/hydration-boundary.spec.tsx | 2 +- web/app/(commonLayout)/hydration-boundary.tsx | 2 +- .../webapp-reset-password/layout.tsx | 2 +- .../components/external-member-sso-auth.tsx | 4 +- .../webapp-signin/components/sso-auth.tsx | 4 +- .../(shareLayout)/webapp-signin/layout.tsx | 2 +- .../webapp-signin/normalForm.tsx | 4 +- web/app/(shareLayout)/webapp-signin/page.tsx | 2 +- .../(commonLayout)/account-page/index.tsx | 2 +- web/app/account/(commonLayout)/header.tsx | 2 +- web/app/account/oauth/authorize/layout.tsx | 2 +- web/app/activate/page.tsx | 2 +- .../app/app-access-control/index.tsx | 2 +- .../components/app/app-publisher/index.tsx | 2 +- web/app/components/app/overview/app-card.tsx | 2 +- .../components/apps/__tests__/list.spec.tsx | 8 +- web/app/components/apps/app-card.tsx | 2 +- web/app/components/apps/list.tsx | 2 +- .../chat/chat-with-history/sidebar/index.tsx | 2 +- .../chat/embedded-chatbot/header/index.tsx | 2 +- .../base/chat/embedded-chatbot/index.tsx | 2 +- .../__tests__/use-web-app-brand.spec.tsx | 4 +- .../hooks/use-web-app-brand.ts | 2 +- .../list/built-in-pipeline-list.tsx | 2 +- web/app/components/datasets/list/index.tsx | 2 +- web/app/components/explore/app-list/index.tsx | 2 +- web/app/components/explore/try-app/index.tsx | 2 +- .../components/header/account-about/index.tsx | 2 +- .../account-dropdown/__tests__/index.spec.tsx | 4 +- .../header/account-dropdown/index.tsx | 2 +- .../data-source-page-new/index.tsx | 2 +- .../account-setting/members-page/index.tsx | 2 +- .../members-page/invite-button.tsx | 2 +- .../operation/transfer-ownership.tsx | 2 +- .../model-provider-page/index.tsx | 2 +- .../model-selector/popup.tsx | 2 +- web/app/components/header/index.tsx | 2 +- .../license-env/__tests__/index.spec.tsx | 4 +- .../components/header/license-env/index.tsx | 4 +- .../use-install-plugin-limit.spec.ts | 5 +- .../hooks/use-install-plugin-limit.tsx | 8 +- .../steps/hooks/use-install-multi-state.ts | 2 +- .../hooks/use-detail-header-state.ts | 2 +- .../operation-dropdown.tsx | 2 +- .../components/plugins/plugin-item/index.tsx | 2 +- .../plugins/plugin-page/context-provider.tsx | 2 +- .../empty/__tests__/index.spec.tsx | 8 +- .../plugins/plugin-page/empty/index.tsx | 2 +- .../components/plugins/plugin-page/index.tsx | 2 +- .../plugin-page/install-plugin-dropdown.tsx | 2 +- .../plugin-page/use-reference-setting.ts | 2 +- .../plugins/reference-setting-modal/index.tsx | 2 +- .../text-generation-sidebar.spec.tsx | 2 +- .../hooks/use-text-generation-app-state.ts | 2 +- .../text-generation-sidebar.tsx | 4 +- web/app/components/tools/provider-list.tsx | 2 +- .../hooks/use-nodes-sync-draft.ts | 2 +- .../block-selector/all-start-blocks.tsx | 2 +- .../workflow/block-selector/all-tools.tsx | 2 +- .../workflow/block-selector/data-sources.tsx | 2 +- .../workflow/block-selector/tabs.tsx | 2 +- .../workflow/block-selector/tool-picker.tsx | 2 +- .../collaboration/hooks/use-collaboration.ts | 2 +- .../__tests__/use-workflow-comment.spec.ts | 12 +- .../workflow/hooks/use-workflow-comment.ts | 2 +- .../hooks/use-workflow-panel-interactions.ts | 2 +- .../agent-strategy-selector.spec.tsx | 2 +- .../components/agent-strategy-selector.tsx | 2 +- .../workflow/operator/zoom-in-out.tsx | 2 +- .../device/__tests__/page-terminal.spec.tsx | 2 +- web/app/device/_header.tsx | 2 +- web/app/device/layout.tsx | 2 +- web/app/device/page.tsx | 2 +- web/app/forgot-password/page.tsx | 2 +- web/app/install/page.tsx | 2 +- web/app/reset-password/layout.tsx | 2 +- web/app/signin/__tests__/_header.spec.tsx | 2 +- web/app/signin/_header.tsx | 2 +- web/app/signin/components/sso-auth.tsx | 4 +- web/app/signin/invite-settings/page.tsx | 2 +- web/app/signin/layout.tsx | 2 +- web/app/signin/normal-form.tsx | 4 +- web/app/signup/components/input-mail.tsx | 2 +- web/app/signup/layout.tsx | 2 +- web/context/app-context-provider.tsx | 2 +- web/context/web-app-context.tsx | 2 +- web/contract/console/system.ts | 10 -- web/contract/router.ts | 2 - .../__tests__/system-features.spec.ts | 34 ++++-- .../system-features/client.ts} | 21 ++-- .../system-features/config.ts} | 58 ++++++++- web/features/system-features/constants.ts | 23 ++++ .../system-features/server.ts} | 17 ++- web/hooks/use-document-title.ts | 4 +- web/service/__tests__/server.spec.ts | 5 +- web/service/access-control.ts | 2 +- web/service/use-explore.ts | 2 +- web/types/feature.ts | 115 ------------------ 100 files changed, 260 insertions(+), 290 deletions(-) delete mode 100644 web/contract/console/system.ts rename web/{service => features/system-features}/__tests__/system-features.spec.ts (91%) rename web/{service/system-features.ts => features/system-features/client.ts} (63%) rename web/{config/cloud-system-features.ts => features/system-features/config.ts} (50%) create mode 100644 web/features/system-features/constants.ts rename web/{service/server-system-features.ts => features/system-features/server.ts} (56%) delete mode 100644 web/types/feature.ts diff --git a/web/__tests__/plugins/plugin-marketplace-to-install.test.tsx b/web/__tests__/plugins/plugin-marketplace-to-install.test.tsx index 3d08fe9d7c..34bf470983 100644 --- a/web/__tests__/plugins/plugin-marketplace-to-install.test.tsx +++ b/web/__tests__/plugins/plugin-marketplace-to-install.test.tsx @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import { pluginInstallLimit } from '@/app/components/plugins/install-plugin/hooks/use-install-plugin-limit' -import { InstallationScope } from '@/types/feature' +import { InstallationScope } from '@/features/system-features/constants' describe('Plugin Marketplace to Install Flow', () => { describe('install permission validation pipeline', () => { diff --git a/web/__tests__/utils/mock-system-features.tsx b/web/__tests__/utils/mock-system-features.tsx index e2e65db8d5..31762627d1 100644 --- a/web/__tests__/utils/mock-system-features.tsx +++ b/web/__tests__/utils/mock-system-features.tsx @@ -1,10 +1,10 @@ +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import type { RenderHookOptions, RenderHookResult, RenderOptions, RenderResult } from '@testing-library/react' import type { ReactElement, ReactNode } from 'react' -import type { SystemFeatures } from '@/types/feature' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, renderHook } from '@testing-library/react' +import { defaultSystemFeatures } from '@/features/system-features/config' import { consoleQuery } from '@/service/client' -import { defaultSystemFeatures } from '@/types/feature' type QueryKeyProvider = { queryKey: () => readonly unknown[] @@ -40,9 +40,9 @@ type DeepPartial = T extends Array : T const buildSystemFeatures = ( - overrides: DeepPartial = {}, -): SystemFeatures => { - const o = overrides as Partial + overrides: DeepPartial = {}, +): GetSystemFeaturesResponse => { + const o = overrides as Partial return { ...defaultSystemFeatures, ...o, @@ -65,6 +65,14 @@ const buildSystemFeatures = ( license: { ...defaultSystemFeatures.license, ...(o.license ?? {}), + workspaces: { + ...defaultSystemFeatures.license.workspaces, + ...(o.license?.workspaces ?? {}), + }, + }, + plugin_manager: { + ...defaultSystemFeatures.plugin_manager, + ...(o.plugin_manager ?? {}), }, } } @@ -90,10 +98,10 @@ export const createTestQueryClient = (): QueryClient => export const seedSystemFeatures = ( queryClient: QueryClient, - overrides: DeepPartial = {}, -): SystemFeatures => { + overrides: DeepPartial = {}, +): GetSystemFeaturesResponse => { const data = buildSystemFeatures(overrides) - queryClient.setQueryData(consoleQuery.systemFeatures.queryKey(), data) + queryClient.setQueryData(consoleQuery.systemFeatures.get.queryKey(), data) return data } @@ -118,7 +126,7 @@ type SystemFeaturesTestOptions = { * `useSuspenseQuery` resolve immediately. Pass `null` to skip seeding and * keep the systemFeatures query in the pending state. */ - systemFeatures?: DeepPartial | null + systemFeatures?: DeepPartial | null trialModels?: readonly string[] | null /** * Seed the workflow clipboard DSL version query only for tests that need it. @@ -130,7 +138,7 @@ type SystemFeaturesTestOptions = { type SystemFeaturesWrapper = { queryClient: QueryClient - systemFeatures: SystemFeatures | null + systemFeatures: GetSystemFeaturesResponse | null wrapper: (props: { children: ReactNode }) => ReactElement } @@ -154,7 +162,7 @@ export const createSystemFeaturesWrapper = ( export const renderWithSystemFeatures = ( ui: ReactElement, options: SystemFeaturesTestOptions & Omit = {}, -): RenderResult & { queryClient: QueryClient, systemFeatures: SystemFeatures | null } => { +): RenderResult & { queryClient: QueryClient, systemFeatures: GetSystemFeaturesResponse | null } => { const { systemFeatures: sf, trialModels, appDslVersion, queryClient: qc, ...renderOptions } = options const { wrapper, queryClient, systemFeatures } = createSystemFeaturesWrapper({ systemFeatures: sf, @@ -169,7 +177,7 @@ export const renderWithSystemFeatures = ( export const renderHookWithSystemFeatures = ( callback: (props: Props) => Result, options: SystemFeaturesTestOptions & Omit, 'wrapper'> = {}, -): RenderHookResult & { queryClient: QueryClient, systemFeatures: SystemFeatures | null } => { +): RenderHookResult & { queryClient: QueryClient, systemFeatures: GetSystemFeaturesResponse | null } => { const { systemFeatures: sf, trialModels, appDslVersion, queryClient: qc, ...hookOptions } = options const { wrapper, queryClient, systemFeatures } = createSystemFeaturesWrapper({ systemFeatures: sf, diff --git a/web/app/(commonLayout)/__tests__/hydration-boundary.spec.tsx b/web/app/(commonLayout)/__tests__/hydration-boundary.spec.tsx index e34bed9120..7546e95d69 100644 --- a/web/app/(commonLayout)/__tests__/hydration-boundary.spec.tsx +++ b/web/app/(commonLayout)/__tests__/hydration-boundary.spec.tsx @@ -51,7 +51,7 @@ vi.mock('@/service/server', () => ({ }, })) -vi.mock('@/service/server-system-features', () => ({ +vi.mock('@/features/system-features/server', () => ({ serverSystemFeaturesQueryOptions: () => ({ queryKey: ['console', 'system-features'], queryFn: mocks.systemFeaturesQueryFn, diff --git a/web/app/(commonLayout)/hydration-boundary.tsx b/web/app/(commonLayout)/hydration-boundary.tsx index 6512fc9428..16bed8a0d3 100644 --- a/web/app/(commonLayout)/hydration-boundary.tsx +++ b/web/app/(commonLayout)/hydration-boundary.tsx @@ -2,10 +2,10 @@ import type { ReactNode } from 'react' import { dehydrate, HydrationBoundary } from '@tanstack/react-query' import { getQueryClientServer } from '@/context/query-client-server' import { serverUserProfileQueryOptions } from '@/features/account-profile/server' +import { serverSystemFeaturesQueryOptions } from '@/features/system-features/server' import { headers } from '@/next/headers' import { redirect } from '@/next/navigation' import { getServerConsoleClientContext, resolveServerConsoleApiUrl, serverConsoleQuery } from '@/service/server' -import { serverSystemFeaturesQueryOptions } from '@/service/server-system-features' import { basePath } from '@/utils/var' const CURRENT_PATHNAME_HEADER = 'x-dify-pathname' diff --git a/web/app/(shareLayout)/webapp-reset-password/layout.tsx b/web/app/(shareLayout)/webapp-reset-password/layout.tsx index 82749c8641..a8388e0957 100644 --- a/web/app/(shareLayout)/webapp-reset-password/layout.tsx +++ b/web/app/(shareLayout)/webapp-reset-password/layout.tsx @@ -3,7 +3,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import Header from '@/app/signin/_header' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' export default function SignInLayout({ children }: any) { const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions()) diff --git a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx index 003aab4cab..ec313ac988 100644 --- a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx @@ -5,10 +5,10 @@ import * as React from 'react' import { useCallback, useEffect } from 'react' import AppUnavailable from '@/app/components/base/app-unavailable' import Loading from '@/app/components/base/loading' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' +import { SSOProtocol } from '@/features/system-features/constants' import { useRouter, useSearchParams } from '@/next/navigation' import { fetchWebOAuth2SSOUrl, fetchWebOIDCSSOUrl, fetchWebSAMLSSOUrl } from '@/service/share' -import { systemFeaturesQueryOptions } from '@/service/system-features' -import { SSOProtocol } from '@/types/feature' const ExternalMemberSSOAuth = () => { const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions()) diff --git a/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx index 52625bbdfb..4cf4ddd65a 100644 --- a/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx @@ -5,12 +5,12 @@ import { toast } from '@langgenius/dify-ui/toast' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' +import { SSOProtocol } from '@/features/system-features/constants' import { useRouter, useSearchParams } from '@/next/navigation' import { fetchMembersOAuth2SSOUrl, fetchMembersOIDCSSOUrl, fetchMembersSAMLSSOUrl } from '@/service/share' -import { SSOProtocol } from '@/types/feature' type SSOAuthProps = { - protocol: SSOProtocol | '' + protocol: string } const SSOAuth: FC = ({ diff --git a/web/app/(shareLayout)/webapp-signin/layout.tsx b/web/app/(shareLayout)/webapp-signin/layout.tsx index 99dd787cec..31bbd79974 100644 --- a/web/app/(shareLayout)/webapp-signin/layout.tsx +++ b/web/app/(shareLayout)/webapp-signin/layout.tsx @@ -4,8 +4,8 @@ import type { PropsWithChildren } from 'react' import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useDocumentTitle from '@/hooks/use-document-title' -import { systemFeaturesQueryOptions } from '@/service/system-features' export default function SignInLayout({ children }: PropsWithChildren) { const { t } = useTranslation() diff --git a/web/app/(shareLayout)/webapp-signin/normalForm.tsx b/web/app/(shareLayout)/webapp-signin/normalForm.tsx index 676db018a9..7785ec9230 100644 --- a/web/app/(shareLayout)/webapp-signin/normalForm.tsx +++ b/web/app/(shareLayout)/webapp-signin/normalForm.tsx @@ -7,9 +7,9 @@ import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { IS_CE_EDITION } from '@/config' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' +import { LicenseStatus } from '@/features/system-features/constants' import Link from '@/next/link' -import { systemFeaturesQueryOptions } from '@/service/system-features' -import { LicenseStatus } from '@/types/feature' import MailAndCodeAuth from './components/mail-and-code-auth' import MailAndPasswordAuth from './components/mail-and-password-auth' import SSOAuth from './components/sso-auth' diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx index cc0a34cd93..f176635b4c 100644 --- a/web/app/(shareLayout)/webapp-signin/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/page.tsx @@ -6,9 +6,9 @@ import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import AppUnavailable from '@/app/components/base/app-unavailable' import { useWebAppStore } from '@/context/web-app-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode } from '@/models/access-control' import { useRouter, useSearchParams } from '@/next/navigation' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { webAppLogout } from '@/service/webapp-auth' import ExternalMemberSsoAuth from './components/external-member-sso-auth' import NormalForm from './normalForm' diff --git a/web/app/account/(commonLayout)/account-page/index.tsx b/web/app/account/(commonLayout)/account-page/index.tsx index 8f12906e3a..d215328acf 100644 --- a/web/app/account/(commonLayout)/account-page/index.tsx +++ b/web/app/account/(commonLayout)/account-page/index.tsx @@ -17,9 +17,9 @@ import Collapse from '@/app/components/header/account-setting/collapse' import { IS_CE_EDITION, validPassword } from '@/config' import { useProviderContext } from '@/context/provider-context' import { userProfileQueryOptions } from '@/features/account-profile/client' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { consoleQuery } from '@/service/client' import { updateUserProfile } from '@/service/common' -import { systemFeaturesQueryOptions } from '@/service/system-features' import DeleteAccount from '../delete-account' import AvatarWithEdit from './AvatarWithEdit' diff --git a/web/app/account/(commonLayout)/header.tsx b/web/app/account/(commonLayout)/header.tsx index 8507ce1a2c..daa2e0ec0c 100644 --- a/web/app/account/(commonLayout)/header.tsx +++ b/web/app/account/(commonLayout)/header.tsx @@ -5,9 +5,9 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import DifyLogo from '@/app/components/base/logo/dify-logo' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' import { useRouter } from '@/next/navigation' -import { systemFeaturesQueryOptions } from '@/service/system-features' import Avatar from './avatar' const Header = () => { diff --git a/web/app/account/oauth/authorize/layout.tsx b/web/app/account/oauth/authorize/layout.tsx index af4b13dabb..053baeca92 100644 --- a/web/app/account/oauth/authorize/layout.tsx +++ b/web/app/account/oauth/authorize/layout.tsx @@ -6,8 +6,8 @@ import Loading from '@/app/components/base/loading' import Header from '@/app/signin/_header' import { AppContextProvider } from '@/context/app-context-provider' import { isLegacyBase401, userProfileQueryOptions } from '@/features/account-profile/client' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useDocumentTitle from '@/hooks/use-document-title' -import { systemFeaturesQueryOptions } from '@/service/system-features' export default function SignInLayout({ children }: any) { const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions()) diff --git a/web/app/activate/page.tsx b/web/app/activate/page.tsx index 995227ec6e..c028074620 100644 --- a/web/app/activate/page.tsx +++ b/web/app/activate/page.tsx @@ -2,7 +2,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import * as React from 'react' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Header from '../signin/_header' import ActivateForm from './activateForm' diff --git a/web/app/components/app/app-access-control/index.tsx b/web/app/components/app/app-access-control/index.tsx index 5a94c7e756..1deb0fa210 100644 --- a/web/app/components/app/app-access-control/index.tsx +++ b/web/app/components/app/app-access-control/index.tsx @@ -8,9 +8,9 @@ import { RiBuildingLine, RiGlobalLine, RiVerifiedBadgeLine } from '@remixicon/re import { useSuspenseQuery } from '@tanstack/react-query' import { useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode, SubjectType } from '@/models/access-control' import { useUpdateAccessMode } from '@/service/access-control' -import { systemFeaturesQueryOptions } from '@/service/system-features' import useAccessControlStore from '../../../../context/access-control-store' import AccessControlDialog from './access-control-dialog' import AccessControlItem from './access-control-item' diff --git a/web/app/components/app/app-publisher/index.tsx b/web/app/components/app/app-publisher/index.tsx index 58b611ac45..bdd24d9412 100644 --- a/web/app/components/app/app-publisher/index.tsx +++ b/web/app/components/app/app-publisher/index.tsx @@ -35,13 +35,13 @@ import { collaborationManager } from '@/app/components/workflow/collaboration/co import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager' import { WorkflowContext } from '@/app/components/workflow/context' import { appDefaultIconBackground } from '@/config' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { AccessMode } from '@/models/access-control' import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control' import { fetchAppDetailDirect, publishToCreatorsPlatform } from '@/service/apps' import { fetchInstalledAppList } from '@/service/explore' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useInvalidateAppWorkflow } from '@/service/use-workflow' import { fetchPublishedWorkflow } from '@/service/workflow' import { AppModeEnum } from '@/types/app' diff --git a/web/app/components/app/overview/app-card.tsx b/web/app/components/app/overview/app-card.tsx index 3bff802c91..d116e16534 100644 --- a/web/app/components/app/overview/app-card.tsx +++ b/web/app/components/app/overview/app-card.tsx @@ -15,11 +15,11 @@ import { useStore as useAppStore } from '@/app/components/app/store' import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button' import { useAppContext } from '@/context/app-context' import { useDocLink } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode } from '@/models/access-control' import { usePathname, useRouter } from '@/next/navigation' import { useAppWhiteListSubjects } from '@/service/access-control' import { fetchAppDetailDirect } from '@/service/apps' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useAppWorkflow } from '@/service/use-workflow' import { AppModeEnum } from '@/types/app' import { asyncRunSafe } from '@/utils' diff --git a/web/app/components/apps/__tests__/list.spec.tsx b/web/app/components/apps/__tests__/list.spec.tsx index 1a8b910bd4..b09b0d0051 100644 --- a/web/app/components/apps/__tests__/list.spec.tsx +++ b/web/app/components/apps/__tests__/list.spec.tsx @@ -22,7 +22,9 @@ vi.mock('@/next/navigation', () => ({ vi.mock('@/service/client', () => ({ consoleClient: { - systemFeatures: vi.fn(), + systemFeatures: { + get: vi.fn(), + }, }, consoleQuery: { apps: { @@ -36,7 +38,9 @@ vi.mock('@/service/client', () => ({ }, }, systemFeatures: { - queryKey: () => ['console', 'systemFeatures'], + get: { + queryKey: () => ['console', 'systemFeatures', 'get'], + }, }, }, })) diff --git a/web/app/components/apps/app-card.tsx b/web/app/components/apps/app-card.tsx index c3dff4d262..3f640e4daf 100644 --- a/web/app/components/apps/app-card.tsx +++ b/web/app/components/apps/app-card.tsx @@ -39,6 +39,7 @@ import { UserAvatarList } from '@/app/components/base/user-avatar-list' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AppCardTags } from '@/features/tag-management/components/app-card-tags' import { useAsyncWindowOpen } from '@/hooks/use-async-window-open' import { AccessMode } from '@/models/access-control' @@ -47,7 +48,6 @@ import { useRouter } from '@/next/navigation' import { useGetUserCanAccessApp } from '@/service/access-control' import { copyApp, exportAppConfig, updateAppInfo } from '@/service/apps' import { fetchInstalledAppList } from '@/service/explore' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useDeleteAppMutation } from '@/service/use-apps' import { fetchWorkflowDraft } from '@/service/workflow' import { AppModeEnum } from '@/types/app' diff --git a/web/app/components/apps/list.tsx b/web/app/components/apps/list.tsx index c10e1c20e5..33469f0ef8 100644 --- a/web/app/components/apps/list.tsx +++ b/web/app/components/apps/list.tsx @@ -12,12 +12,12 @@ import Input from '@/app/components/base/input' import TabSliderNew from '@/app/components/base/tab-slider-new' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { useAppContext } from '@/context/app-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { TagFilter } from '@/features/tag-management/components/tag-filter' import { CheckModal } from '@/hooks/use-pay' import dynamic from '@/next/dynamic' import { usePathname, useRouter, useSearchParams } from '@/next/navigation' import { consoleQuery } from '@/service/client' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { AppModeEnum } from '@/types/app' import AppCard from './app-card' import { AppCardSkeleton } from './app-card-skeleton' diff --git a/web/app/components/base/chat/chat-with-history/sidebar/index.tsx b/web/app/components/base/chat/chat-with-history/sidebar/index.tsx index d44d3f86fa..48a76b993a 100644 --- a/web/app/components/base/chat/chat-with-history/sidebar/index.tsx +++ b/web/app/components/base/chat/chat-with-history/sidebar/index.tsx @@ -22,7 +22,7 @@ import List from '@/app/components/base/chat/chat-with-history/sidebar/list' import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal' import DifyLogo from '@/app/components/base/logo/dify-logo' import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useChatWithHistoryContext } from '../context' type Props = { diff --git a/web/app/components/base/chat/embedded-chatbot/header/index.tsx b/web/app/components/base/chat/embedded-chatbot/header/index.tsx index 22c1e95bc8..2e47dc05a5 100644 --- a/web/app/components/base/chat/embedded-chatbot/header/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/header/index.tsx @@ -10,7 +10,7 @@ import ActionButton from '@/app/components/base/action-button' import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown' import Divider from '@/app/components/base/divider' import DifyLogo from '@/app/components/base/logo/dify-logo' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { isClient } from '@/utils/client' import { useEmbeddedChatbotContext, diff --git a/web/app/components/base/chat/embedded-chatbot/index.tsx b/web/app/components/base/chat/embedded-chatbot/index.tsx index 886549ca96..651a6b1404 100644 --- a/web/app/components/base/chat/embedded-chatbot/index.tsx +++ b/web/app/components/base/chat/embedded-chatbot/index.tsx @@ -11,10 +11,10 @@ import Header from '@/app/components/base/chat/embedded-chatbot/header' import Loading from '@/app/components/base/loading' import DifyLogo from '@/app/components/base/logo/dify-logo' import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useDocumentTitle from '@/hooks/use-document-title' import { AppSourceType } from '@/service/share' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { EmbeddedChatbotContext, useEmbeddedChatbotContext, diff --git a/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx b/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx index 99cbc03b32..758d0126c2 100644 --- a/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx +++ b/web/app/components/custom/custom-web-app-brand/hooks/__tests__/use-web-app-brand.spec.tsx @@ -1,6 +1,6 @@ +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import type { ChangeEvent } from 'react' import type { AppContextValue } from '@/context/app-context' -import type { SystemFeatures } from '@/types/feature' import { act } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createMockProviderContextValue } from '@/__mocks__/provider-context' @@ -18,7 +18,7 @@ import { useProviderContext } from '@/context/provider-context' import { updateCurrentWorkspace } from '@/service/common' import useWebAppBrand from '../use-web-app-brand' -let currentBrandingOverrides: Partial = {} +let currentBrandingOverrides: Partial = {} const renderHook = (callback: (props: Props) => Result) => renderHookWithSystemFeatures(callback, { systemFeatures: { diff --git a/web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts b/web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts index e24edab421..f8b0ebce5e 100644 --- a/web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts +++ b/web/app/components/custom/custom-web-app-brand/hooks/use-web-app-brand.ts @@ -7,8 +7,8 @@ import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/i import { Plan } from '@/app/components/billing/type' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { updateCurrentWorkspace } from '@/service/common' -import { systemFeaturesQueryOptions } from '@/service/system-features' const MAX_LOGO_FILE_SIZE = 5 * 1024 * 1024 const CUSTOM_CONFIG_URL = '/workspaces/custom-config' diff --git a/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx b/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx index 3d14dd2f95..8ef69eaaf8 100644 --- a/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/built-in-pipeline-list.tsx @@ -1,8 +1,8 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { useLocale } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { LanguagesSupported } from '@/i18n-config/language' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { usePipelineTemplateList } from '@/service/use-pipeline' import CreateCard from './create-card' import TemplateCard from './template-card' diff --git a/web/app/components/datasets/list/index.tsx b/web/app/components/datasets/list/index.tsx index 147491419e..2b05d89b5c 100644 --- a/web/app/components/datasets/list/index.tsx +++ b/web/app/components/datasets/list/index.tsx @@ -11,11 +11,11 @@ import Input from '@/app/components/base/input' import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label' import { useAppContext, useSelector as useAppContextSelector } from '@/context/app-context' import { useExternalApiPanel } from '@/context/external-api-panel-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { TagFilter } from '@/features/tag-management/components/tag-filter' import { TagManagementModal } from '@/features/tag-management/components/tag-management-modal' import useDocumentTitle from '@/hooks/use-document-title' import { useDatasetApiBaseUrl, useInvalidDatasetList } from '@/service/knowledge/use-dataset' -import { systemFeaturesQueryOptions } from '@/service/system-features' // Components import ExternalAPIPanel from '../external-api/external-api-panel' import ServiceApi from '../extra-info/service-api' diff --git a/web/app/components/explore/app-list/index.tsx b/web/app/components/explore/app-list/index.tsx index 41485e0e89..23608c0db7 100644 --- a/web/app/components/explore/app-list/index.tsx +++ b/web/app/components/explore/app-list/index.tsx @@ -20,12 +20,12 @@ import Banner from '@/app/components/explore/banner/banner' import Category from '@/app/components/explore/category' import CreateAppModal from '@/app/components/explore/create-app-modal' import { useAppContext } from '@/context/app-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useImportDSL } from '@/hooks/use-import-dsl' import { DSLImportMode, } from '@/models/app' import { fetchAppDetail } from '@/service/explore' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useMembers } from '@/service/use-common' import { useExploreAppList } from '@/service/use-explore' import { trackCreateApp } from '@/utils/create-app-tracking' diff --git a/web/app/components/explore/try-app/index.tsx b/web/app/components/explore/try-app/index.tsx index 0c11ebb289..0d7d816fe1 100644 --- a/web/app/components/explore/try-app/index.tsx +++ b/web/app/components/explore/try-app/index.tsx @@ -10,7 +10,7 @@ import { useState } from 'react' import AppUnavailable from '@/app/components/base/app-unavailable' import Loading from '@/app/components/base/loading' import { IS_CLOUD_EDITION } from '@/config' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useGetTryAppInfo } from '@/service/use-try-app' import App from './app' import AppInfo from './app-info' diff --git a/web/app/components/header/account-about/index.tsx b/web/app/components/header/account-about/index.tsx index fb72c60237..0a610d0f8e 100644 --- a/web/app/components/header/account-about/index.tsx +++ b/web/app/components/header/account-about/index.tsx @@ -9,8 +9,8 @@ import { useTranslation } from 'react-i18next' import DifyLogo from '@/app/components/base/logo/dify-logo' import { IS_CE_EDITION } from '@/config' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' -import { systemFeaturesQueryOptions } from '@/service/system-features' type IAccountSettingProps = { langGeniusVersionInfo: LangGeniusVersionResponse diff --git a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx index a894832936..a79111d57a 100644 --- a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx +++ b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx @@ -1,7 +1,7 @@ +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import type { AppContextValue } from '@/context/app-context' import type { ModalContextState } from '@/context/modal-context' import type { ProviderContextState } from '@/context/provider-context' -import type { SystemFeatures } from '@/types/feature' import { fireEvent, screen, waitFor } from '@testing-library/react' import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import { Plan } from '@/app/components/billing/type' @@ -144,7 +144,7 @@ describe('AccountDropdown', () => { const renderWithRouter = ( ui: React.ReactElement, - options: { systemFeatures?: DeepPartial } = {}, + options: { systemFeatures?: DeepPartial } = {}, ) => { return renderWithSystemFeatures(ui, { systemFeatures: options.systemFeatures ?? { branding: { enabled: false } }, diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index 74794cdb51..b706fd33cc 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -17,9 +17,9 @@ import { useDocLink } from '@/context/i18n' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' import { env } from '@/env' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' import { useRouter } from '@/next/navigation' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useLogout } from '@/service/use-common' import AccountAbout from '../account-about' import GithubStar from '../github-star' diff --git a/web/app/components/header/account-setting/data-source-page-new/index.tsx b/web/app/components/header/account-setting/data-source-page-new/index.tsx index a12adfacbd..f65f84674f 100644 --- a/web/app/components/header/account-setting/data-source-page-new/index.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/index.tsx @@ -1,6 +1,6 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { memo } from 'react' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useGetDataSourceListAuth } from '@/service/use-datasource' import Card from './card' import InstallFromMarketplace from './install-from-marketplace' diff --git a/web/app/components/header/account-setting/members-page/index.tsx b/web/app/components/header/account-setting/members-page/index.tsx index bac95a0f9b..f7413a51b0 100644 --- a/web/app/components/header/account-setting/members-page/index.tsx +++ b/web/app/components/header/account-setting/members-page/index.tsx @@ -11,9 +11,9 @@ import UpgradeBtn from '@/app/components/billing/upgrade-btn' import { useAppContext } from '@/context/app-context' import { useLocale } from '@/context/i18n' import { useProviderContext } from '@/context/provider-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import { LanguagesSupported } from '@/i18n-config/language' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useMembers } from '@/service/use-common' import EditWorkspaceModal from './edit-workspace-modal' import InviteButton from './invite-button' diff --git a/web/app/components/header/account-setting/members-page/invite-button.tsx b/web/app/components/header/account-setting/members-page/invite-button.tsx index da2cea5639..e0a9408515 100644 --- a/web/app/components/header/account-setting/members-page/invite-button.tsx +++ b/web/app/components/header/account-setting/members-page/invite-button.tsx @@ -4,7 +4,7 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { useAppContext } from '@/context/app-context' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useWorkspacePermissions } from '@/service/use-workspace' type InviteButtonProps = { diff --git a/web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx b/web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx index 7b0359c511..c901f83434 100644 --- a/web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx +++ b/web/app/components/header/account-setting/members-page/operation/transfer-ownership.tsx @@ -13,7 +13,7 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' import { useAppContext } from '@/context/app-context' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useWorkspacePermissions } from '@/service/use-workspace' type Props = { diff --git a/web/app/components/header/account-setting/model-provider-page/index.tsx b/web/app/components/header/account-setting/model-provider-page/index.tsx index 5390d52707..000a9d0d7e 100644 --- a/web/app/components/header/account-setting/model-provider-page/index.tsx +++ b/web/app/components/header/account-setting/model-provider-page/index.tsx @@ -10,8 +10,8 @@ import { useTranslation } from 'react-i18next' import { usePluginsWithLatestVersion } from '@/app/components/plugins/hooks' import { IS_CLOUD_EDITION } from '@/config' import { useProviderContext } from '@/context/provider-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { consoleQuery } from '@/service/client' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { CustomConfigurationStatusEnum, ModelTypeEnum, diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx index c7bd482370..8b9cf2306c 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/popup.tsx @@ -13,9 +13,9 @@ import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/ import { IS_CLOUD_EDITION } from '@/config' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useSearchParams } from '@/next/navigation' import { consoleQuery } from '@/service/client' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useInstallPackageFromMarketPlace } from '@/service/use-plugins' import { CustomConfigurationStatusEnum, ModelFeatureEnum, ModelStatusEnum, ModelTypeEnum } from '../declarations' import { useLanguage, useMarketplaceAllPlugins } from '../hooks' diff --git a/web/app/components/header/index.tsx b/web/app/components/header/index.tsx index fc0b9f2c58..22392ca240 100644 --- a/web/app/components/header/index.tsx +++ b/web/app/components/header/index.tsx @@ -8,9 +8,9 @@ import { useAppContext } from '@/context/app-context' import { useModalContext } from '@/context/modal-context' import { useProviderContext } from '@/context/provider-context' import { WorkspaceProvider } from '@/context/workspace-context-provider' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import Link from '@/next/link' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { Plan } from '../billing/type' import AccountDropdown from './account-dropdown' import AppNav from './app-nav' diff --git a/web/app/components/header/license-env/__tests__/index.spec.tsx b/web/app/components/header/license-env/__tests__/index.spec.tsx index ec8959da78..fc3d1e9552 100644 --- a/web/app/components/header/license-env/__tests__/index.spec.tsx +++ b/web/app/components/header/license-env/__tests__/index.spec.tsx @@ -1,7 +1,7 @@ import { screen } from '@testing-library/react' import dayjs from 'dayjs' import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' -import { LicenseStatus } from '@/types/feature' +import { LicenseStatus } from '@/features/system-features/constants' import LicenseNav from '../index' describe('LicenseNav', () => { @@ -26,7 +26,7 @@ describe('LicenseNav', () => { systemFeatures: { license: { status: LicenseStatus.ACTIVE, - expired_at: null, + expired_at: '', }, }, }) diff --git a/web/app/components/header/license-env/index.tsx b/web/app/components/header/license-env/index.tsx index e7112a61f1..e3ed3c59c4 100644 --- a/web/app/components/header/license-env/index.tsx +++ b/web/app/components/header/license-env/index.tsx @@ -4,8 +4,8 @@ import { RiHourglass2Fill } from '@remixicon/react' import { useSuspenseQuery } from '@tanstack/react-query' import dayjs from 'dayjs' import { useTranslation } from 'react-i18next' -import { systemFeaturesQueryOptions } from '@/service/system-features' -import { LicenseStatus } from '@/types/feature' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' +import { LicenseStatus } from '@/features/system-features/constants' import PremiumBadge from '../../base/premium-badge' const LicenseNav = () => { diff --git a/web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts b/web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts index c6881e5c88..fc147773c7 100644 --- a/web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts +++ b/web/app/components/plugins/install-plugin/hooks/__tests__/use-install-plugin-limit.spec.ts @@ -1,6 +1,7 @@ +import type { PluginInstallationScope } from '@dify/contracts/api/console/system-features/types.gen' import { describe, expect, it } from 'vitest' import { renderHookWithSystemFeatures as renderHook } from '@/__tests__/utils/mock-system-features' -import { InstallationScope } from '@/types/feature' +import { InstallationScope } from '@/features/system-features/constants' import { pluginInstallLimit } from '../use-install-plugin-limit' const basePlugin = { @@ -117,7 +118,7 @@ describe('pluginInstallLimit', () => { const features = { plugin_installation_permission: { restrict_to_marketplace_only: false, - plugin_installation_scope: 'unknown-scope' as InstallationScope, + plugin_installation_scope: 'unknown-scope' as unknown as PluginInstallationScope, }, } diff --git a/web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx b/web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx index ccc28c74c1..2b2c479909 100644 --- a/web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx +++ b/web/app/components/plugins/install-plugin/hooks/use-install-plugin-limit.tsx @@ -1,12 +1,12 @@ +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import type { Plugin, PluginManifestInMarket } from '../../types' -import type { SystemFeatures } from '@/types/feature' import { useSuspenseQuery } from '@tanstack/react-query' -import { systemFeaturesQueryOptions } from '@/service/system-features' -import { InstallationScope } from '@/types/feature' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' +import { InstallationScope } from '@/features/system-features/constants' type PluginProps = (Plugin | PluginManifestInMarket) & { from: 'github' | 'marketplace' | 'package' } -export function pluginInstallLimit(plugin: PluginProps, systemFeatures: SystemFeatures) { +export function pluginInstallLimit(plugin: PluginProps, systemFeatures: GetSystemFeaturesResponse) { if (systemFeatures.plugin_installation_permission.restrict_to_marketplace_only) { if (plugin.from === 'github' || plugin.from === 'package') return { canInstall: false } diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/hooks/use-install-multi-state.ts b/web/app/components/plugins/install-plugin/install-bundle/steps/hooks/use-install-multi-state.ts index 196f453325..4d8751fd4a 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/hooks/use-install-multi-state.ts +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/hooks/use-install-multi-state.ts @@ -5,7 +5,7 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { useCallback, useEffect, useMemo, useState } from 'react' import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed' import { pluginInstallLimit } from '@/app/components/plugins/install-plugin/hooks/use-install-plugin-limit' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins' type UseInstallMultiStateParams = { diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-detail-header-state.ts b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-detail-header-state.ts index 8ebcbe0dc1..772c3c2573 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-detail-header-state.ts +++ b/web/app/components/plugins/plugin-detail-panel/detail-header/hooks/use-detail-header-state.ts @@ -4,7 +4,7 @@ import type { PluginDetail } from '../../../types' import { useSuspenseQuery } from '@tanstack/react-query' import { useBoolean } from 'ahooks' import { useCallback, useMemo, useState } from 'react' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useReferenceSetting from '../../../plugin-page/use-reference-setting' import { AUTO_UPDATE_MODE } from '../../../reference-setting-modal/auto-update-setting/types' import { PluginSource } from '../../../types' diff --git a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx index 20c2ac3a5b..43e953cac2 100644 --- a/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx +++ b/web/app/components/plugins/plugin-detail-panel/operation-dropdown.tsx @@ -12,7 +12,7 @@ import { import { useSuspenseQuery } from '@tanstack/react-query' import * as React from 'react' import { useTranslation } from 'react-i18next' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { PluginSource } from '../types' type Props = { diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index a4b084db46..b47c53570c 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -17,9 +17,9 @@ import { useTranslation } from 'react-i18next' import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' import { API_PREFIX } from '@/config' import { useAppContext } from '@/context/app-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useRenderI18nObject } from '@/hooks/use-i18n' import useTheme from '@/hooks/use-theme' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { isEqualOrLaterThanVersion } from '@/utils/semver' import { getMarketplaceUrl } from '@/utils/var' import Badge from '../../base/badge' diff --git a/web/app/components/plugins/plugin-page/context-provider.tsx b/web/app/components/plugins/plugin-page/context-provider.tsx index 6347fde86f..b5869d64d7 100644 --- a/web/app/components/plugins/plugin-page/context-provider.tsx +++ b/web/app/components/plugins/plugin-page/context-provider.tsx @@ -10,7 +10,7 @@ import { useRef, useState, } from 'react' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { PLUGIN_PAGE_TABS_MAP, usePluginPageTabs } from '../hooks' import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/constants' import { diff --git a/web/app/components/plugins/plugin-page/empty/__tests__/index.spec.tsx b/web/app/components/plugins/plugin-page/empty/__tests__/index.spec.tsx index 73a5dd379d..24d3414516 100644 --- a/web/app/components/plugins/plugin-page/empty/__tests__/index.spec.tsx +++ b/web/app/components/plugins/plugin-page/empty/__tests__/index.spec.tsx @@ -1,10 +1,10 @@ +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import type { ReactElement } from 'react' import type { FilterState } from '../../filter-management' -import type { SystemFeatures } from '@/types/feature' import { act, fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' -import { InstallationScope } from '@/types/feature' +import { InstallationScope } from '@/features/system-features/constants' // ==================== Imports (after mocks) ==================== @@ -32,7 +32,7 @@ const { plugin_installation_scope: 'all' as const, restrict_to_marketplace_only: false, }, - } as Partial, + } as Partial, pluginList: { plugins: [] as Array<{ id: string }> } as { plugins: Array<{ id: string }> } | undefined, } return { @@ -105,7 +105,7 @@ const setMockFilters = (filters: Partial) => { mockState.filters = { ...mockState.filters, ...filters } } -const setMockSystemFeatures = (features: Partial) => { +const setMockSystemFeatures = (features: Partial) => { mockState.systemFeatures = { ...mockState.systemFeatures, ...features } } diff --git a/web/app/components/plugins/plugin-page/empty/index.tsx b/web/app/components/plugins/plugin-page/empty/index.tsx index bebeedcb85..88eb5e2d57 100644 --- a/web/app/components/plugins/plugin-page/empty/index.tsx +++ b/web/app/components/plugins/plugin-page/empty/index.tsx @@ -12,7 +12,7 @@ import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndD import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-from-github' import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useInstalledPluginList } from '@/service/use-plugins' import Line from '../../marketplace/empty/line' import { usePluginPageContext } from '../context' diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index 4c5624bad5..20f0cf703f 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -19,11 +19,11 @@ import TabSlider from '@/app/components/base/tab-slider' import ReferenceSettingModal from '@/app/components/plugins/reference-setting-modal' import { MARKETPLACE_API_PREFIX, SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' import { useDocLink } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useDocumentTitle from '@/hooks/use-document-title' import { usePluginInstallation } from '@/hooks/use-query-params' import Link from '@/next/link' import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { sleep } from '@/utils' import { PLUGIN_PAGE_TABS_MAP } from '../hooks' import InstallFromLocalPackage from '../install-plugin/install-from-local-package' diff --git a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx index 3582d127f2..488ca91160 100644 --- a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx +++ b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx @@ -18,7 +18,7 @@ import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndD import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-from-github' import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' type Props = { onSwitchToMarketplaceTab: () => void diff --git a/web/app/components/plugins/plugin-page/use-reference-setting.ts b/web/app/components/plugins/plugin-page/use-reference-setting.ts index 7c62c1879a..1685c3356b 100644 --- a/web/app/components/plugins/plugin-page/use-reference-setting.ts +++ b/web/app/components/plugins/plugin-page/use-reference-setting.ts @@ -3,7 +3,7 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { useAppContext } from '@/context/app-context' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useInvalidateReferenceSettings, useMutationReferenceSettings, useReferenceSettings } from '@/service/use-plugins' import { PermissionType } from '../types' diff --git a/web/app/components/plugins/reference-setting-modal/index.tsx b/web/app/components/plugins/reference-setting-modal/index.tsx index e7d15f5402..0ebe08e8f0 100644 --- a/web/app/components/plugins/reference-setting-modal/index.tsx +++ b/web/app/components/plugins/reference-setting-modal/index.tsx @@ -10,7 +10,7 @@ import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { PermissionType } from '@/app/components/plugins/types' import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import AutoUpdateSetting from './auto-update-setting' import { defaultValue as autoUpdateDefaultValue } from './auto-update-setting/config' import Label from './label' diff --git a/web/app/components/share/text-generation/__tests__/text-generation-sidebar.spec.tsx b/web/app/components/share/text-generation/__tests__/text-generation-sidebar.spec.tsx index f4e5844d36..d9e6c13749 100644 --- a/web/app/components/share/text-generation/__tests__/text-generation-sidebar.spec.tsx +++ b/web/app/components/share/text-generation/__tests__/text-generation-sidebar.spec.tsx @@ -3,9 +3,9 @@ import type { PromptConfig, SavedMessage } from '@/models/debug' import type { SiteInfo } from '@/models/share' import type { VisionSettings } from '@/types/app' import { fireEvent, render, screen } from '@testing-library/react' +import { defaultSystemFeatures } from '@/features/system-features/config' import { AccessMode } from '@/models/access-control' import { Resolution, TransferMethod } from '@/types/app' -import { defaultSystemFeatures } from '@/types/feature' import TextGenerationSidebar from '../text-generation-sidebar' const runOncePropsSpy = vi.fn() diff --git a/web/app/components/share/text-generation/hooks/use-text-generation-app-state.ts b/web/app/components/share/text-generation/hooks/use-text-generation-app-state.ts index 12e0f22d16..9033bbee6e 100644 --- a/web/app/components/share/text-generation/hooks/use-text-generation-app-state.ts +++ b/web/app/components/share/text-generation/hooks/use-text-generation-app-state.ts @@ -8,11 +8,11 @@ import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { getRawInputsFromUrlParams } from '@/app/components/base/chat/utils' import { useWebAppStore } from '@/context/web-app-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useAppFavicon } from '@/hooks/use-app-favicon' import useDocumentTitle from '@/hooks/use-document-title' import { changeLanguage } from '@/i18n-config/client' import { AppSourceType, fetchSavedMessage as doFetchSavedMessage, removeMessage, saveMessage } from '@/service/share' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { Resolution, TransferMethod } from '@/types/app' import { userInputsFormToPromptVariables } from '@/utils/model-config' diff --git a/web/app/components/share/text-generation/text-generation-sidebar.tsx b/web/app/components/share/text-generation/text-generation-sidebar.tsx index 8ce0e93738..e714081277 100644 --- a/web/app/components/share/text-generation/text-generation-sidebar.tsx +++ b/web/app/components/share/text-generation/text-generation-sidebar.tsx @@ -1,9 +1,9 @@ +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import type { FC, RefObject } from 'react' import type { InputValueTypes, TextGenerationCustomConfig, TextGenerationRunControl } from './types' import type { PromptConfig, SavedMessage, TextToSpeechConfig } from '@/models/debug' import type { SiteInfo } from '@/models/share' import type { VisionFile, VisionSettings } from '@/types/app' -import type { SystemFeatures } from '@/types/feature' import { cn } from '@langgenius/dify-ui/cn' import { useTranslation } from 'react-i18next' import SavedItems from '@/app/components/app/text-generate/saved-items' @@ -38,7 +38,7 @@ type TextGenerationSidebarProps = { runControl: TextGenerationRunControl | null savedMessages: SavedMessage[] siteInfo: SiteInfo - systemFeatures: SystemFeatures + systemFeatures: GetSystemFeaturesResponse textToSpeechConfig: TextToSpeechConfig | null visionConfig: VisionSettings } diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 991dc03824..9d5c54b214 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -17,7 +17,7 @@ import CustomCreateCard from '@/app/components/tools/provider/custom-create-card import ProviderDetail from '@/app/components/tools/provider/detail' import WorkflowToolEmpty from '@/app/components/tools/provider/empty' import ToolCardSkeletonGrid from '@/app/components/tools/provider/tool-card-skeleton' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useCheckInstalled, useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useAllToolProviders } from '@/service/use-tools' import Marketplace from './marketplace' diff --git a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts index 0ac528c303..d9eafcfc7d 100644 --- a/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts +++ b/web/app/components/workflow-app/hooks/use-nodes-sync-draft.ts @@ -10,8 +10,8 @@ import { useSerialAsyncCallback } from '@/app/components/workflow/hooks/use-seri import { useNodesReadOnly } from '@/app/components/workflow/hooks/use-workflow' import { useWorkflowStore } from '@/app/components/workflow/store' import { API_PREFIX } from '@/config' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { postWithKeepalive } from '@/service/fetch' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { syncWorkflowDraft } from '@/service/workflow' import { useWorkflowRefreshDraft } from '.' diff --git a/web/app/components/workflow/block-selector/all-start-blocks.tsx b/web/app/components/workflow/block-selector/all-start-blocks.tsx index 2f431ebb49..cb93219e62 100644 --- a/web/app/components/workflow/block-selector/all-start-blocks.tsx +++ b/web/app/components/workflow/block-selector/all-start-blocks.tsx @@ -19,8 +19,8 @@ import { import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useFeaturedTriggersRecommendations } from '@/service/use-plugins' import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers' import { getMarketplaceUrl } from '@/utils/var' diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index 98a68103c7..9869afdeff 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -21,8 +21,8 @@ import Divider from '@/app/components/base/divider' import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general' import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' import { useGetLanguage } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { getMarketplaceUrl } from '@/utils/var' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' import { PluginCategoryEnum } from '../../plugins/types' diff --git a/web/app/components/workflow/block-selector/data-sources.tsx b/web/app/components/workflow/block-selector/data-sources.tsx index 1ee2bf9b9b..6a5116922f 100644 --- a/web/app/components/workflow/block-selector/data-sources.tsx +++ b/web/app/components/workflow/block-selector/data-sources.tsx @@ -14,7 +14,7 @@ import { } from 'react' import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' import { useGetLanguage } from '@/context/i18n' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useMarketplacePlugins } from '../../plugins/marketplace/hooks' import { PluginCategoryEnum } from '../../plugins/types' import { BlockEnum } from '../types' diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index 0b38a2df2c..11e7e5484d 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -10,7 +10,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/too import { useSuspenseQuery } from '@tanstack/react-query' import { memo, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useFeaturedToolsRecommendations } from '@/service/use-plugins' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools' import { basePath } from '@/utils/var' diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index 93ee1882db..dd8a6b6dd4 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -20,7 +20,7 @@ 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 AllTools from '@/app/components/workflow/block-selector/all-tools' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { createCustomCollection, } from '@/service/tools' diff --git a/web/app/components/workflow/collaboration/hooks/use-collaboration.ts b/web/app/components/workflow/collaboration/hooks/use-collaboration.ts index 0030ffb77c..4cc576fbf3 100644 --- a/web/app/components/workflow/collaboration/hooks/use-collaboration.ts +++ b/web/app/components/workflow/collaboration/hooks/use-collaboration.ts @@ -7,7 +7,7 @@ import type { } from '../types/collaboration' import { useSuspenseQuery } from '@tanstack/react-query' import { useEffect, useRef, useState } from 'react' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { collaborationManager } from '../core/collaboration-manager' import { CursorService } from '../services/cursor-service' diff --git a/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts b/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts index 19eb1ad0e8..4b92183e60 100644 --- a/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-workflow-comment.spec.ts @@ -54,9 +54,11 @@ vi.mock('@/context/app-context', () => ({ vi.mock('@/service/client', () => ({ consoleClient: { - systemFeatures: () => ({ - enable_collaboration_mode: globalFeatureState.enableCollaboration, - }), + systemFeatures: { + get: () => ({ + enable_collaboration_mode: globalFeatureState.enableCollaboration, + }), + }, workflowComments: { create: (...args: unknown[]) => mockCreateWorkflowComment(...args), delete: (...args: unknown[]) => mockDeleteWorkflowComment(...args), @@ -73,7 +75,9 @@ vi.mock('@/service/client', () => ({ }, consoleQuery: { systemFeatures: { - queryKey: () => ['console', 'systemFeatures'], + get: { + queryKey: () => ['console', 'systemFeatures', 'get'], + }, }, }, })) diff --git a/web/app/components/workflow/hooks/use-workflow-comment.ts b/web/app/components/workflow/hooks/use-workflow-comment.ts index 54be5325e5..c446ef7ee5 100644 --- a/web/app/components/workflow/hooks/use-workflow-comment.ts +++ b/web/app/components/workflow/hooks/use-workflow-comment.ts @@ -4,9 +4,9 @@ import { useCallback, useEffect, useMemo, useRef } from 'react' import { useReactFlow } from 'reactflow' import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager' import { useAppContext } from '@/context/app-context' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useParams } from '@/next/navigation' import { consoleClient } from '@/service/client' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useStore } from '../store' import { ControlMode } from '../types' diff --git a/web/app/components/workflow/hooks/use-workflow-panel-interactions.ts b/web/app/components/workflow/hooks/use-workflow-panel-interactions.ts index e7ca512d34..89f8a9a445 100644 --- a/web/app/components/workflow/hooks/use-workflow-panel-interactions.ts +++ b/web/app/components/workflow/hooks/use-workflow-panel-interactions.ts @@ -1,6 +1,6 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { useCallback } from 'react' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useStore, useWorkflowStore } from '../store' import { ControlMode } from '../types' import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync' diff --git a/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx b/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx index 995d3c8a70..58296f5af5 100644 --- a/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/__tests__/agent-strategy-selector.spec.tsx @@ -19,7 +19,7 @@ vi.mock('@tanstack/react-query', () => ({ useSuspenseQuery: mocks.useSuspenseQuery, })) -vi.mock('@/service/system-features', () => ({ +vi.mock('@/features/system-features/client', () => ({ systemFeaturesQueryOptions: () => ({}), })) diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx index a01cc739f8..f3cd453e67 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy-selector.tsx @@ -24,8 +24,8 @@ import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hook import { PluginCategoryEnum } from '@/app/components/plugins/types' import { CollectionType } from '@/app/components/tools/types' import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useStrategyProviders } from '@/service/use-strategy' import Tools from '../../../block-selector/tools' import ViewTypeSelect, { ViewType } from '../../../block-selector/view-type-select' diff --git a/web/app/components/workflow/operator/zoom-in-out.tsx b/web/app/components/workflow/operator/zoom-in-out.tsx index 48d790fd18..51b8809951 100644 --- a/web/app/components/workflow/operator/zoom-in-out.tsx +++ b/web/app/components/workflow/operator/zoom-in-out.tsx @@ -16,7 +16,7 @@ import { useReactFlow, useViewport, } from 'reactflow' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useNodesSyncDraft, useWorkflowReadOnly, diff --git a/web/app/device/__tests__/page-terminal.spec.tsx b/web/app/device/__tests__/page-terminal.spec.tsx index cb69653ead..e638bc1dfc 100644 --- a/web/app/device/__tests__/page-terminal.spec.tsx +++ b/web/app/device/__tests__/page-terminal.spec.tsx @@ -35,7 +35,7 @@ vi.mock('@/service/device-flow', () => ({ }, })) -vi.mock('@/service/system-features', () => ({ +vi.mock('@/features/system-features/client', () => ({ systemFeaturesQueryOptions: () => ({ queryKey: ['sys'], queryFn: async () => ({}) }), })) diff --git a/web/app/device/_header.tsx b/web/app/device/_header.tsx index f3880db64c..60e729366d 100644 --- a/web/app/device/_header.tsx +++ b/web/app/device/_header.tsx @@ -3,10 +3,10 @@ import { useSuspenseQuery } from '@tanstack/react-query' import Divider from '@/app/components/base/divider' import LocaleMenu from '@/app/signin/_locale-menu' import { useLocale } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { setLocaleOnClient } from '@/i18n-config' import { languages } from '@/i18n-config/language' import dynamic from '@/next/dynamic' -import { systemFeaturesQueryOptions } from '@/service/system-features' const DifyLogo = dynamic(() => import('@/app/components/base/logo/dify-logo'), { ssr: false, diff --git a/web/app/device/layout.tsx b/web/app/device/layout.tsx index fe4b759dac..25eccf5551 100644 --- a/web/app/device/layout.tsx +++ b/web/app/device/layout.tsx @@ -1,8 +1,8 @@ 'use client' import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useDocumentTitle from '@/hooks/use-document-title' -import { systemFeaturesQueryOptions } from '@/service/system-features' import Header from './_header' export default function DeviceLayout({ children }: { children: React.ReactNode }) { diff --git a/web/app/device/page.tsx b/web/app/device/page.tsx index e8f41d6a90..5296165ac2 100644 --- a/web/app/device/page.tsx +++ b/web/app/device/page.tsx @@ -5,10 +5,10 @@ import { useQuery } from '@tanstack/react-query' import { useEffect, useState } from 'react' import Divider from '@/app/components/base/divider' import { userProfileQueryOptions } from '@/features/account-profile/client' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { usePathname, useRouter, useSearchParams } from '@/next/navigation' import { consoleQuery } from '@/service/client' import { deviceLookup } from '@/service/device-flow' -import { systemFeaturesQueryOptions } from '@/service/system-features' import AuthorizeAccount from './components/authorize-account' import AuthorizeSSO from './components/authorize-sso' import Chooser from './components/chooser' diff --git a/web/app/forgot-password/page.tsx b/web/app/forgot-password/page.tsx index 3f8f17c98e..5a307bf2d9 100644 --- a/web/app/forgot-password/page.tsx +++ b/web/app/forgot-password/page.tsx @@ -3,9 +3,9 @@ import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import * as React from 'react' import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useDocumentTitle from '@/hooks/use-document-title' import { useSearchParams } from '@/next/navigation' -import { systemFeaturesQueryOptions } from '@/service/system-features' import Header from '../signin/_header' import ForgotPasswordForm from './ForgotPasswordForm' diff --git a/web/app/install/page.tsx b/web/app/install/page.tsx index 8d1f71f817..9c35868e60 100644 --- a/web/app/install/page.tsx +++ b/web/app/install/page.tsx @@ -2,7 +2,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import * as React from 'react' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Header from '../signin/_header' import InstallForm from './installForm' diff --git a/web/app/reset-password/layout.tsx b/web/app/reset-password/layout.tsx index e2691a397e..c795b1d477 100644 --- a/web/app/reset-password/layout.tsx +++ b/web/app/reset-password/layout.tsx @@ -2,7 +2,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Header from '../signin/_header' export default function SignInLayout({ children }: any) { diff --git a/web/app/signin/__tests__/_header.spec.tsx b/web/app/signin/__tests__/_header.spec.tsx index 06a54e0b36..a407ff830f 100644 --- a/web/app/signin/__tests__/_header.spec.tsx +++ b/web/app/signin/__tests__/_header.spec.tsx @@ -19,7 +19,7 @@ vi.mock('@/next/dynamic', () => ({ default: () => () => null, })) -vi.mock('@/service/system-features', () => ({ +vi.mock('@/features/system-features/client', () => ({ systemFeaturesQueryOptions: () => ({}), })) diff --git a/web/app/signin/_header.tsx b/web/app/signin/_header.tsx index 22a05c8d2d..df2a4ca976 100644 --- a/web/app/signin/_header.tsx +++ b/web/app/signin/_header.tsx @@ -2,10 +2,10 @@ import { useSuspenseQuery } from '@tanstack/react-query' import Divider from '@/app/components/base/divider' import { useLocale } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { setLocaleOnClient } from '@/i18n-config' import { languages } from '@/i18n-config/language' import dynamic from '@/next/dynamic' -import { systemFeaturesQueryOptions } from '@/service/system-features' import LocaleMenu from './_locale-menu' // Avoid rendering the logo and theme selector on the server diff --git a/web/app/signin/components/sso-auth.tsx b/web/app/signin/components/sso-auth.tsx index 5cacd6a01d..f48ace97e4 100644 --- a/web/app/signin/components/sso-auth.tsx +++ b/web/app/signin/components/sso-auth.tsx @@ -5,12 +5,12 @@ import { toast } from '@langgenius/dify-ui/toast' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' +import { SSOProtocol } from '@/features/system-features/constants' import { useRouter, useSearchParams } from '@/next/navigation' import { getUserOAuth2SSOUrl, getUserOIDCSSOUrl, getUserSAMLSSOUrl } from '@/service/sso' -import { SSOProtocol } from '@/types/feature' type SSOAuthProps = { - protocol: SSOProtocol | '' + protocol: string } const SSOAuth: FC = ({ diff --git a/web/app/signin/invite-settings/page.tsx b/web/app/signin/invite-settings/page.tsx index 86dcf64194..f4f255bd89 100644 --- a/web/app/signin/invite-settings/page.tsx +++ b/web/app/signin/invite-settings/page.tsx @@ -12,13 +12,13 @@ import Input from '@/app/components/base/input' import Loading from '@/app/components/base/loading' import { LICENSE_LINK } from '@/constants/link' import { useLocale } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { i18n, setLocaleOnClient } from '@/i18n-config' import { languages } from '@/i18n-config/language' import Link from '@/next/link' import { useRouter, useSearchParams } from '@/next/navigation' import { consoleQuery } from '@/service/client' import { activateMember } from '@/service/common' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useInvitationCheck } from '@/service/use-common' import { getBrowserTimezone, timezones } from '@/utils/timezone' import { resolvePostLoginRedirect } from '../utils/post-login-redirect' diff --git a/web/app/signin/layout.tsx b/web/app/signin/layout.tsx index a6537793c8..8160a2b59b 100644 --- a/web/app/signin/layout.tsx +++ b/web/app/signin/layout.tsx @@ -2,8 +2,8 @@ import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useDocumentTitle from '@/hooks/use-document-title' -import { systemFeaturesQueryOptions } from '@/service/system-features' import Header from './_header' export default function SignInLayout({ children }: any) { diff --git a/web/app/signin/normal-form.tsx b/web/app/signin/normal-form.tsx index fb4e9f872c..511fe75311 100644 --- a/web/app/signin/normal-form.tsx +++ b/web/app/signin/normal-form.tsx @@ -7,11 +7,11 @@ import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { IS_CE_EDITION } from '@/config' import { isLegacyBase401, userProfileQueryOptions } from '@/features/account-profile/client' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' +import { LicenseStatus } from '@/features/system-features/constants' import Link from '@/next/link' import { useRouter, useSearchParams } from '@/next/navigation' import { invitationCheck } from '@/service/common' -import { systemFeaturesQueryOptions } from '@/service/system-features' -import { LicenseStatus } from '@/types/feature' import Loading from '../components/base/loading' import MailAndCodeAuth from './components/mail-and-code-auth' import MailAndPasswordAuth from './components/mail-and-password-auth' diff --git a/web/app/signup/components/input-mail.tsx b/web/app/signup/components/input-mail.tsx index a0a09001d2..0612bf5d2f 100644 --- a/web/app/signup/components/input-mail.tsx +++ b/web/app/signup/components/input-mail.tsx @@ -9,8 +9,8 @@ import Input from '@/app/components/base/input' import Split from '@/app/signin/split' import { emailRegex } from '@/config' import { useLocale } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useSendMail } from '@/service/use-common' type Props = { diff --git a/web/app/signup/layout.tsx b/web/app/signup/layout.tsx index 3c1b17ae10..68571dd570 100644 --- a/web/app/signup/layout.tsx +++ b/web/app/signup/layout.tsx @@ -3,8 +3,8 @@ import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import Header from '@/app/signin/_header' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import useDocumentTitle from '@/hooks/use-document-title' -import { systemFeaturesQueryOptions } from '@/service/system-features' export default function RegisterLayout({ children }: any) { const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions()) diff --git a/web/context/app-context-provider.tsx b/web/context/app-context-provider.tsx index b578bf3478..6f2b85e8ed 100644 --- a/web/context/app-context-provider.tsx +++ b/web/context/app-context-provider.tsx @@ -18,8 +18,8 @@ import { } from '@/context/app-context' import { env } from '@/env' import { userProfileQueryOptions } from '@/features/account-profile/client' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { consoleQuery } from '@/service/client' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useLangGeniusVersion, } from '@/service/use-common' diff --git a/web/context/web-app-context.tsx b/web/context/web-app-context.tsx index f562c07832..2adaf16f59 100644 --- a/web/context/web-app-context.tsx +++ b/web/context/web-app-context.tsx @@ -8,9 +8,9 @@ import { useEffect } from 'react' import { create } from 'zustand' import { getProcessedSystemVariablesFromUrlParams } from '@/app/components/base/chat/utils' import Loading from '@/app/components/base/loading' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode } from '@/models/access-control' import { usePathname, useSearchParams } from '@/next/navigation' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { useGetWebAppAccessModeByCode } from '@/service/use-share' type WebAppStore = { diff --git a/web/contract/console/system.ts b/web/contract/console/system.ts deleted file mode 100644 index 73e920b804..0000000000 --- a/web/contract/console/system.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { SystemFeatures } from '@/types/feature' -import { type } from '@orpc/contract' -import { base } from '../base' - -export const systemFeaturesContract = base - .route({ - path: '/system-features', - method: 'GET', - }) - .output(type()) diff --git a/web/contract/router.ts b/web/contract/router.ts index e6b353a993..58395b32c9 100644 --- a/web/contract/router.ts +++ b/web/contract/router.ts @@ -18,7 +18,6 @@ import { import { changePreferredProviderTypeContract, modelProvidersModelsContract } from './console/model-providers' import { notificationContract, notificationDismissContract } from './console/notification' import { pluginCheckInstalledContract, pluginLatestVersionsContract } from './console/plugins' -import { systemFeaturesContract } from './console/system' import { tagBindingCreateContract, tagBindingRemoveContract, @@ -80,7 +79,6 @@ export const consoleRouterContract = { get: accountProfileContract, }, }, - systemFeatures: systemFeaturesContract, apps: { ...communityContract.apps, list: appListContract, diff --git a/web/service/__tests__/system-features.spec.ts b/web/features/system-features/__tests__/system-features.spec.ts similarity index 91% rename from web/service/__tests__/system-features.spec.ts rename to web/features/system-features/__tests__/system-features.spec.ts index 87bf4b1ffa..fa0938fb12 100644 --- a/web/service/__tests__/system-features.spec.ts +++ b/web/features/system-features/__tests__/system-features.spec.ts @@ -1,11 +1,11 @@ -import type { SystemFeatures } from '@/types/feature' +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import { describe, expect, it, vi } from 'vitest' -import { defaultSystemFeatures } from '@/types/feature' +import { defaultSystemFeatures } from '../config' type LoadOptions = { cloudEnv?: Partial isCloudEdition: boolean - systemFeaturesResult?: SystemFeatures + systemFeaturesResult?: GetSystemFeaturesResponse systemFeaturesError?: Error } @@ -52,18 +52,22 @@ const loadSystemFeaturesModule = async ({ ...cloudEnv, }, })) - vi.doMock('../client', () => ({ + vi.doMock('@/service/client', () => ({ consoleClient: { - systemFeatures, + systemFeatures: { + get: systemFeatures, + }, }, consoleQuery: { systemFeatures: { - queryKey: () => queryKey, + get: { + queryKey: () => queryKey, + }, }, }, })) - const module = await import('../system-features') + const module = await import('../client') return { module, @@ -93,19 +97,23 @@ const loadServerSystemFeaturesModule = async ({ ...cloudEnv, }, })) - vi.doMock('../server', () => ({ + vi.doMock('@/service/server', () => ({ getServerConsoleClientContext, serverConsoleClient: { - systemFeatures, + systemFeatures: { + get: systemFeatures, + }, }, serverConsoleQuery: { systemFeatures: { - queryKey: () => queryKey, + get: { + queryKey: () => queryKey, + }, }, }, })) - const module = await import('../server-system-features') + const module = await import('../server') return { getServerConsoleClientContext, @@ -124,7 +132,7 @@ describe('systemFeaturesQueryOptions', () => { const data = await options.queryFn?.(queryContext) expect(systemFeatures).not.toHaveBeenCalled() - expect(options.staleTime).toBe(Infinity) + expect(options.staleTime).toBe('static') expect(data).toMatchObject({ enable_marketplace: true, enable_email_code_login: true, @@ -215,7 +223,7 @@ describe('serverSystemFeaturesQueryOptions', () => { expect(getServerConsoleClientContext).not.toHaveBeenCalled() expect(systemFeatures).not.toHaveBeenCalled() - expect(options.staleTime).toBe(Infinity) + expect(options.staleTime).toBe('static') expect(data).toMatchObject({ enable_marketplace: false, enable_email_password_login: true, diff --git a/web/service/system-features.ts b/web/features/system-features/client.ts similarity index 63% rename from web/service/system-features.ts rename to web/features/system-features/client.ts index 97bf4f97a5..2af36ea9fb 100644 --- a/web/service/system-features.ts +++ b/web/features/system-features/client.ts @@ -1,9 +1,8 @@ -import type { SystemFeatures } from '@/types/feature' +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import { queryOptions } from '@tanstack/react-query' import { IS_CLOUD_EDITION } from '@/config' -import { cloudSystemFeatures } from '@/config/cloud-system-features' -import { defaultSystemFeatures } from '@/types/feature' -import { consoleClient, consoleQuery } from './client' +import { consoleClient, consoleQuery } from '@/service/client' +import { cloudSystemFeatures, defaultSystemFeatures } from './config' /** * Soft-fallback to defaults so the dashboard stays usable when /system-features fails. @@ -15,26 +14,26 @@ import { consoleClient, consoleQuery } from './client' * is a small, dependency-free endpoint in the community edition. * * For Cloud, this query is intentionally local-only and uses `staleTime: - * Infinity`: the payload comes from frontend config/defaults, so refetching - * would only re-run the same local merge. For non-Cloud, do not override + * 'static'`: the payload comes from frontend config/defaults, so invalidation + * should not re-run the same local merge. For non-Cloud, do not override * `staleTime`: inherit the 5-minute default from query-client-server.ts. */ export const systemFeaturesQueryOptions = () => { - const queryKey = consoleQuery.systemFeatures.queryKey() + const queryKey = consoleQuery.systemFeatures.get.queryKey() if (IS_CLOUD_EDITION) { - return queryOptions({ + return queryOptions({ queryKey, queryFn: async () => cloudSystemFeatures, - staleTime: Infinity, + staleTime: 'static', }) } - return queryOptions({ + return queryOptions({ queryKey, queryFn: async () => { try { - return await consoleClient.systemFeatures() + return await consoleClient.systemFeatures.get() } catch (err) { console.error('[systemFeatures] fetch failed, using defaults', err) diff --git a/web/config/cloud-system-features.ts b/web/features/system-features/config.ts similarity index 50% rename from web/config/cloud-system-features.ts rename to web/features/system-features/config.ts index 8508789903..3932259800 100644 --- a/web/config/cloud-system-features.ts +++ b/web/features/system-features/config.ts @@ -1,8 +1,58 @@ -import type { SystemFeatures } from '@/types/feature' +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import { env } from '@/env' -import { defaultSystemFeatures, InstallationScope, LicenseStatus } from '@/types/feature' +import { InstallationScope, LicenseStatus } from './constants' -export const cloudSystemFeatures: SystemFeatures = { +export const defaultSystemFeatures = { + sso_enforced_for_signin: false, + sso_enforced_for_signin_protocol: '', + enable_marketplace: false, + enable_email_code_login: false, + enable_email_password_login: true, + enable_social_oauth_login: false, + enable_collaboration_mode: true, + is_allow_create_workspace: false, + is_allow_register: false, + is_email_setup: false, + enable_change_email: true, + max_plugin_package_size: 15728640, + license: { + status: LicenseStatus.NONE, + expired_at: '', + workspaces: { + enabled: false, + size: 0, + limit: 0, + }, + }, + branding: { + enabled: false, + login_page_logo: '', + workspace_logo: '', + favicon: '', + application_title: '', + }, + webapp_auth: { + enabled: false, + allow_sso: false, + sso_config: { + protocol: '', + }, + allow_email_code_login: false, + allow_email_password_login: false, + }, + plugin_installation_permission: { + plugin_installation_scope: InstallationScope.ALL, + restrict_to_marketplace_only: false, + }, + plugin_manager: { + enabled: false, + }, + enable_creators_platform: false, + enable_trial_app: false, + enable_explore_banner: false, +} satisfies GetSystemFeaturesResponse + +export const cloudSystemFeatures = { ...defaultSystemFeatures, sso_enforced_for_signin: false, sso_enforced_for_signin_protocol: '', @@ -49,4 +99,4 @@ export const cloudSystemFeatures: SystemFeatures = { enable_creators_platform: env.NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED, enable_trial_app: env.NEXT_PUBLIC_ENABLE_TRIAL_APP, enable_explore_banner: env.NEXT_PUBLIC_ENABLE_EXPLORE_BANNER, -} +} satisfies GetSystemFeaturesResponse diff --git a/web/features/system-features/constants.ts b/web/features/system-features/constants.ts new file mode 100644 index 0000000000..8c2401d63c --- /dev/null +++ b/web/features/system-features/constants.ts @@ -0,0 +1,23 @@ +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' + +export const SSOProtocol = { + SAML: 'saml', + OIDC: 'oidc', + OAuth2: 'oauth2', +} as const + +export const LicenseStatus = { + NONE: 'none', + INACTIVE: 'inactive', + ACTIVE: 'active', + EXPIRING: 'expiring', + EXPIRED: 'expired', + LOST: 'lost', +} as const satisfies Record + +export const InstallationScope = { + ALL: 'all', + NONE: 'none', + OFFICIAL_ONLY: 'official_only', + OFFICIAL_AND_PARTNER: 'official_and_specific_partners', +} as const satisfies Record diff --git a/web/service/server-system-features.ts b/web/features/system-features/server.ts similarity index 56% rename from web/service/server-system-features.ts rename to web/features/system-features/server.ts index 62a6e2e2f2..097bc6eea1 100644 --- a/web/service/server-system-features.ts +++ b/web/features/system-features/server.ts @@ -1,30 +1,29 @@ -import type { SystemFeatures } from '@/types/feature' +import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen' import { queryOptions } from '@tanstack/react-query' import { IS_CLOUD_EDITION } from '@/config' -import { cloudSystemFeatures } from '@/config/cloud-system-features' -import { defaultSystemFeatures } from '@/types/feature' import { getServerConsoleClientContext, serverConsoleClient, serverConsoleQuery, -} from './server' +} from '@/service/server' +import { cloudSystemFeatures, defaultSystemFeatures } from './config' export const serverSystemFeaturesQueryOptions = () => { - const queryKey = serverConsoleQuery.systemFeatures.queryKey() + const queryKey = serverConsoleQuery.systemFeatures.get.queryKey() if (IS_CLOUD_EDITION) { - return queryOptions({ + return queryOptions({ queryKey, queryFn: async () => cloudSystemFeatures, - staleTime: Infinity, + staleTime: 'static', }) } - return queryOptions({ + return queryOptions({ queryKey, queryFn: async () => { try { - return await serverConsoleClient.systemFeatures(undefined, { + return await serverConsoleClient.systemFeatures.get(undefined, { context: await getServerConsoleClientContext(), }) } diff --git a/web/hooks/use-document-title.ts b/web/hooks/use-document-title.ts index ad275f55c9..c98b04341d 100644 --- a/web/hooks/use-document-title.ts +++ b/web/hooks/use-document-title.ts @@ -2,8 +2,8 @@ import { useQuery } from '@tanstack/react-query' import { useFavicon, useTitle } from 'ahooks' import { useEffect } from 'react' -import { systemFeaturesQueryOptions } from '@/service/system-features' -import { defaultSystemFeatures } from '@/types/feature' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' +import { defaultSystemFeatures } from '@/features/system-features/config' import { basePath } from '@/utils/var' export default function useDocumentTitle(title: string) { diff --git a/web/service/__tests__/server.spec.ts b/web/service/__tests__/server.spec.ts index cd1502f53c..0237a62171 100644 --- a/web/service/__tests__/server.spec.ts +++ b/web/service/__tests__/server.spec.ts @@ -61,7 +61,8 @@ describe('server console oRPC client', () => { }) it('should call contracts with forwarded cookies, csrf header, and no-store cache', async () => { - const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ feature: { billing: false } }), { + const { defaultSystemFeatures } = await import('@/features/system-features/config') + const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify(defaultSystemFeatures), { status: 200, headers: { 'content-type': 'application/json', @@ -70,7 +71,7 @@ describe('server console oRPC client', () => { vi.stubGlobal('fetch', fetchMock) const { getServerConsoleClientContext, serverConsoleClient } = await import('../server') - await serverConsoleClient.systemFeatures(undefined, { + await serverConsoleClient.systemFeatures.get(undefined, { context: await getServerConsoleClientContext(), }) diff --git a/web/service/access-control.ts b/web/service/access-control.ts index fa7cfd7055..40ef1cfdea 100644 --- a/web/service/access-control.ts +++ b/web/service/access-control.ts @@ -1,7 +1,7 @@ import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control' import type { App } from '@/types/app' import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { systemFeaturesQueryOptions } from '@/service/system-features' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { get, post } from './base' import { getUserCanAccess } from './share' diff --git a/web/service/use-explore.ts b/web/service/use-explore.ts index de508b642f..006e8d8017 100644 --- a/web/service/use-explore.ts +++ b/web/service/use-explore.ts @@ -1,8 +1,8 @@ import type { App, AppCategory } from '@/models/explore' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useLocale } from '@/context/i18n' +import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode } from '@/models/access-control' -import { systemFeaturesQueryOptions } from '@/service/system-features' import { consoleQuery } from './client' import { fetchAppList, fetchBanners, fetchInstalledAppList, fetchInstalledAppMeta, fetchInstalledAppParams, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore' diff --git a/web/types/feature.ts b/web/types/feature.ts deleted file mode 100644 index cc9b870d69..0000000000 --- a/web/types/feature.ts +++ /dev/null @@ -1,115 +0,0 @@ -export const SSOProtocol = { - SAML: 'saml', - OIDC: 'oidc', - OAuth2: 'oauth2', -} as const - -export type SSOProtocol = typeof SSOProtocol[keyof typeof SSOProtocol] - -export const LicenseStatus = { - NONE: 'none', - INACTIVE: 'inactive', - ACTIVE: 'active', - EXPIRING: 'expiring', - EXPIRED: 'expired', - LOST: 'lost', -} as const - -export type LicenseStatus = typeof LicenseStatus[keyof typeof LicenseStatus] - -export const InstallationScope = { - ALL: 'all', - NONE: 'none', - OFFICIAL_ONLY: 'official_only', - OFFICIAL_AND_PARTNER: 'official_and_specific_partners', -} as const - -export type InstallationScope = typeof InstallationScope[keyof typeof InstallationScope] - -type License = { - status: LicenseStatus - expired_at: string | null -} - -export type SystemFeatures = { - plugin_installation_permission: { - plugin_installation_scope: InstallationScope - restrict_to_marketplace_only: boolean - } - sso_enforced_for_signin: boolean - sso_enforced_for_signin_protocol: SSOProtocol | '' - sso_enforced_for_web: boolean - sso_enforced_for_web_protocol: SSOProtocol | '' - enable_marketplace: boolean - enable_change_email: boolean - enable_email_code_login: boolean - enable_email_password_login: boolean - enable_social_oauth_login: boolean - enable_collaboration_mode: boolean - is_allow_create_workspace: boolean - is_allow_register: boolean - is_email_setup: boolean - license: License - branding: { - enabled: boolean - login_page_logo: string - workspace_logo: string - favicon: string - application_title: string - } - webapp_auth: { - enabled: boolean - allow_sso: boolean - sso_config: { - protocol: SSOProtocol | '' - } - allow_email_code_login: boolean - allow_email_password_login: boolean - } - enable_creators_platform: boolean - enable_trial_app: boolean - enable_explore_banner: boolean -} - -export const defaultSystemFeatures: SystemFeatures = { - plugin_installation_permission: { - plugin_installation_scope: InstallationScope.ALL, - restrict_to_marketplace_only: false, - }, - sso_enforced_for_signin: false, - sso_enforced_for_signin_protocol: '', - sso_enforced_for_web: false, - sso_enforced_for_web_protocol: '', - enable_marketplace: false, - enable_change_email: false, - enable_email_code_login: false, - enable_email_password_login: false, - enable_social_oauth_login: false, - enable_collaboration_mode: false, - is_allow_create_workspace: false, - is_allow_register: false, - is_email_setup: false, - license: { - status: LicenseStatus.NONE, - expired_at: '', - }, - branding: { - enabled: false, - login_page_logo: '', - workspace_logo: '', - favicon: '', - application_title: 'test title', - }, - webapp_auth: { - enabled: false, - allow_sso: false, - sso_config: { - protocol: '', - }, - allow_email_code_login: false, - allow_email_password_login: false, - }, - enable_creators_platform: false, - enable_trial_app: false, - enable_explore_banner: false, -}