mirror of
https://github.com/langgenius/dify.git
synced 2026-09-09 05:41:00 +08:00
refactor(web): move provider feature flags to query consumers (#41937)
This commit is contained in:
parent
d43a23d8f5
commit
e26dcf55a9
@ -11,14 +11,6 @@ export const baseProviderContextValue: ProviderContextState = {
|
||||
isSuccessModelProviders: false,
|
||||
textGenerationModelList: [],
|
||||
isAPIKeySet: true,
|
||||
|
||||
enableSkill: false,
|
||||
enableReplaceWebAppLogo: false,
|
||||
modelLoadBalancingEnabled: false,
|
||||
enableEducationPlan: false,
|
||||
isAllowTransferWorkspace: false,
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate: false,
|
||||
humanInputEmailDeliveryEnabled: false,
|
||||
}
|
||||
|
||||
export const createMockProviderContextValue = (
|
||||
|
||||
@ -16,7 +16,6 @@ import AppIcon from '@/app/components/base/app-icon'
|
||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
import Collapse from '@/app/components/header/account-setting/collapse'
|
||||
import { validPassword } from '@/config'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { updateUserProfile } from '@/service/common'
|
||||
@ -55,10 +54,14 @@ export default function AccountPage() {
|
||||
const userProfile = userProfileResp.profile
|
||||
const mutateUserProfile = () =>
|
||||
queryClient.invalidateQueries({ queryKey: userProfileQueryOptions().queryKey })
|
||||
const { enableEducationPlan } = useProviderContext()
|
||||
const { data: enableEducationPlan } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.education.enabled,
|
||||
}),
|
||||
)
|
||||
const { data: isEducationAccount = false } = useQuery(
|
||||
consoleQuery.account.education.get.queryOptions({
|
||||
enabled: enableEducationPlan,
|
||||
enabled: enableEducationPlan === true,
|
||||
select: ({ is_student }) => is_student ?? false,
|
||||
}),
|
||||
)
|
||||
|
||||
@ -11,7 +11,6 @@ import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { resetUser } from '@/app/components/base/amplitude/utils'
|
||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
@ -23,10 +22,14 @@ export default function AppSelector() {
|
||||
// Cache is hydrated by CommonLayoutHydrationBoundary; this hits cache synchronously.
|
||||
const { data: userProfileResp } = useSuspenseQuery(userProfileQueryOptions())
|
||||
const userProfile = userProfileResp.profile
|
||||
const { enableEducationPlan } = useProviderContext()
|
||||
const { data: enableEducationPlan } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.education.enabled,
|
||||
}),
|
||||
)
|
||||
const { data: isEducationAccount = false } = useQuery(
|
||||
consoleQuery.account.education.get.queryOptions({
|
||||
enabled: enableEducationPlan,
|
||||
enabled: enableEducationPlan === true,
|
||||
select: ({ is_student }) => is_student ?? false,
|
||||
}),
|
||||
)
|
||||
|
||||
@ -43,14 +43,6 @@ const defaultProviderContext = {
|
||||
isSuccessModelProviders: false,
|
||||
textGenerationModelList: [],
|
||||
isAPIKeySet: false,
|
||||
|
||||
enableSkill: false,
|
||||
enableReplaceWebAppLogo: false,
|
||||
modelLoadBalancingEnabled: false,
|
||||
enableEducationPlan: false,
|
||||
isAllowTransferWorkspace: false,
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate: false,
|
||||
humanInputEmailDeliveryEnabled: false,
|
||||
}
|
||||
|
||||
type MockOverrides = {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { ActionItem, SearchResult } from '../actions/types'
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
import { DialogTrigger } from '@langgenius/dify-ui/dialog'
|
||||
import { detectPlatform } from '@tanstack/react-hotkeys'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
||||
import { gotoAnythingDialogHandle } from '../dialog-handle'
|
||||
import { GotoAnything } from '../index'
|
||||
|
||||
@ -79,28 +79,34 @@ function setRemoteResults(results: TestSearchResult[]) {
|
||||
})
|
||||
}
|
||||
|
||||
vi.mock('@tanstack/react-query', () => ({
|
||||
keepPreviousData: (previousData: unknown) => previousData,
|
||||
useQuery: (options: {
|
||||
queryKey: [key: keyof typeof remoteQueryStates, searchTerm: string]
|
||||
enabled?: boolean
|
||||
placeholderData?: (previousData: unknown) => unknown
|
||||
}) => {
|
||||
const provider = options.queryKey[0]
|
||||
if (!options.enabled) return emptyRemoteQueryState()
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
||||
return {
|
||||
...actual,
|
||||
keepPreviousData: (previousData: unknown) => previousData,
|
||||
useQuery: (options: {
|
||||
queryKey: readonly unknown[]
|
||||
enabled?: boolean
|
||||
placeholderData?: (previousData: unknown) => unknown
|
||||
}) => {
|
||||
if (typeof options.queryKey[0] !== 'string')
|
||||
return actual.useQuery({ ...options, placeholderData: undefined })
|
||||
const provider = options.queryKey[0] as keyof typeof remoteQueryStates
|
||||
if (!options.enabled) return emptyRemoteQueryState()
|
||||
|
||||
enabledRemoteQueryKeys.push(provider)
|
||||
enabledRemoteSearches.push(options.queryKey)
|
||||
const state = remoteQueryStates[provider]
|
||||
let data = state.data
|
||||
if (state.isFetching && data.length === 0 && options.placeholderData)
|
||||
data = (options.placeholderData(previousRemoteData[provider]) as TestSearchResult[]) ?? []
|
||||
if (!state.isLoading && !state.isFetching && !state.isError)
|
||||
previousRemoteData[provider] = state.data
|
||||
enabledRemoteQueryKeys.push(provider)
|
||||
enabledRemoteSearches.push([provider, options.queryKey[1] as string])
|
||||
const state = remoteQueryStates[provider]
|
||||
let data = state.data
|
||||
if (state.isFetching && data.length === 0 && options.placeholderData)
|
||||
data = (options.placeholderData(previousRemoteData[provider]) as TestSearchResult[]) ?? []
|
||||
if (!state.isLoading && !state.isFetching && !state.isError)
|
||||
previousRemoteData[provider] = state.data
|
||||
|
||||
return { ...state, data }
|
||||
},
|
||||
}))
|
||||
return { ...state, data }
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../actions/app', () => ({
|
||||
appSearchQueryOptions: (searchTerm: string) => ({ queryKey: ['app', searchTerm] }),
|
||||
@ -145,12 +151,6 @@ vi.mock('@/features/agent-v2/permissions', () => ({
|
||||
useCanManageAgents: () => visibilityState.canManageAgents,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: vi.fn((selector: (state: Partial<ProviderContextState>) => unknown) =>
|
||||
selector({ enableSkill: visibilityState.enableSkill }),
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/app/components/plugins/install-plugin/hooks/use-workspace-plugin-install-permission',
|
||||
() => ({
|
||||
@ -249,7 +249,12 @@ vi.mock('../../plugins/install-plugin/install-from-marketplace', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
const renderGotoAnything = (ui: React.ReactElement) => render(ui)
|
||||
const renderGotoAnything = (ui: React.ReactElement) => {
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
features: { enable_skill: visibilityState.enableSkill },
|
||||
})
|
||||
return render(ui, { wrapper })
|
||||
}
|
||||
|
||||
describe('GotoAnything', () => {
|
||||
beforeEach(() => {
|
||||
|
||||
@ -40,11 +40,11 @@ import { useTranslation } from 'react-i18next'
|
||||
import { MAIN_NAV_ROUTES } from '@/app/components/main-nav/routes'
|
||||
import { selectWorkflowNode } from '@/app/components/workflow/utils/node-navigation'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { isCurrentWorkspaceDatasetOperatorAtom } from '@/context/workspace-state'
|
||||
import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag'
|
||||
import { useCanManageAgents } from '@/features/agent-v2/permissions'
|
||||
import { usePathname, useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { PluginInstallPermissionProvider } from '../plugins/install-plugin/components/plugin-install-permission-provider'
|
||||
import useWorkspacePluginInstallPermission from '../plugins/install-plugin/hooks/use-workspace-plugin-install-permission'
|
||||
import InstallFromMarketplace from '../plugins/install-plugin/install-from-marketplace'
|
||||
@ -233,9 +233,13 @@ function GotoAnythingDialog() {
|
||||
const defaultLocale = useGetLanguage()
|
||||
const canManageAgents = useCanManageAgents()
|
||||
const isCurrentWorkspaceDatasetOperator = useAtomValue(isCurrentWorkspaceDatasetOperatorAtom)
|
||||
const enableSkill = useProviderContextSelector((state) => state.enableSkill)
|
||||
const { data: enableSkill } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.enable_skill,
|
||||
}),
|
||||
)
|
||||
const agentsAvailable = isAgentV2Enabled() && canManageAgents
|
||||
const skillsAvailable = enableSkill && !isCurrentWorkspaceDatasetOperator
|
||||
const skillsAvailable = enableSkill === true && !isCurrentWorkspaceDatasetOperator
|
||||
const isWorkflowPage =
|
||||
appWorkflowPathPattern.test(pathname) || sharedWorkflowPathPattern.test(pathname)
|
||||
const isRagPipelinePage = ragPipelinePathPattern.test(pathname)
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { resetUser } from '@/app/components/base/amplitude/utils'
|
||||
import AccountSection from '@/app/components/main-nav/components/account-section'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useLogout } from '@/service/use-common'
|
||||
import { createAccountProfileQueryClient } from '@/test/console/account-profile'
|
||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
@ -21,10 +19,6 @@ vi.mock('@/app/components/base/amplitude/utils', () => ({
|
||||
resetUser: mockResetUser,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-common', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('@/service/use-common')>()),
|
||||
useLogout: vi.fn(),
|
||||
@ -69,7 +63,7 @@ const renderAccountDropdown = () => {
|
||||
</button>
|
||||
)}
|
||||
/>,
|
||||
{ queryClient },
|
||||
{ queryClient, features: { education: { enabled: false } } },
|
||||
)
|
||||
}
|
||||
|
||||
@ -79,9 +73,6 @@ describe('AccountDropdown', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseRouter.mockReturnValue({ push: mockPush })
|
||||
vi.mocked(useProviderContext).mockReturnValue({
|
||||
enableEducationPlan: false,
|
||||
} as ProviderContextState)
|
||||
vi.mocked(useLogout).mockReturnValue({
|
||||
mutateAsync: mockLogout,
|
||||
} as unknown as ReturnType<typeof useLogout>)
|
||||
@ -90,7 +81,10 @@ describe('AccountDropdown', () => {
|
||||
it('includes the visible account name in the main navigation trigger accessible name', () => {
|
||||
const queryClient = createAccountProfileQueryClient(userProfile)
|
||||
|
||||
renderWithConsoleQuery(<AccountSection />, { queryClient })
|
||||
renderWithConsoleQuery(<AccountSection />, {
|
||||
queryClient,
|
||||
features: { education: { enabled: false } },
|
||||
})
|
||||
|
||||
expect(screen.getByRole('button', { name: accountMenuAccessibleName })).toBeInTheDocument()
|
||||
})
|
||||
@ -98,7 +92,10 @@ describe('AccountDropdown', () => {
|
||||
it('keeps the account identity in the compact trigger accessible name', () => {
|
||||
const queryClient = createAccountProfileQueryClient(userProfile)
|
||||
|
||||
renderWithConsoleQuery(<AccountSection compact />, { queryClient })
|
||||
renderWithConsoleQuery(<AccountSection compact />, {
|
||||
queryClient,
|
||||
features: { education: { enabled: false } },
|
||||
})
|
||||
|
||||
expect(screen.getByRole('button', { name: accountMenuAccessibleName })).toBeInTheDocument()
|
||||
expect(screen.queryByText('Current User')).not.toBeInTheDocument()
|
||||
@ -108,7 +105,10 @@ describe('AccountDropdown', () => {
|
||||
const user = userEvent.setup()
|
||||
const queryClient = createAccountProfileQueryClient(userProfile)
|
||||
|
||||
renderWithConsoleQuery(<AccountSection />, { queryClient })
|
||||
renderWithConsoleQuery(<AccountSection />, {
|
||||
queryClient,
|
||||
features: { education: { enabled: false } },
|
||||
})
|
||||
|
||||
expect(screen.getByText('Current User')).toBeInTheDocument()
|
||||
|
||||
|
||||
@ -25,7 +25,6 @@ import {
|
||||
settingsQueryParamName,
|
||||
settingsQueryParser,
|
||||
} from '@/app/components/header/account-setting/query-params'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import Link from '@/next/link'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
@ -124,10 +123,14 @@ export function MainNavMenuContent({ onLogout }: MainNavMenuContentProps) {
|
||||
...userProfileQueryOptions(),
|
||||
select: (data) => data.profile,
|
||||
})
|
||||
const { enableEducationPlan } = useProviderContext()
|
||||
const { data: enableEducationPlan } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.education.enabled,
|
||||
}),
|
||||
)
|
||||
const { data: isEducationAccount = false } = useQuery(
|
||||
consoleQuery.account.education.get.queryOptions({
|
||||
enabled: enableEducationPlan,
|
||||
enabled: enableEducationPlan === true,
|
||||
select: ({ is_student }) => is_student ?? false,
|
||||
}),
|
||||
)
|
||||
|
||||
@ -3,24 +3,17 @@ import type { ConsoleStateFixture } from '@/test/console/state-fixture'
|
||||
import { fireEvent, screen, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { useState } from 'react'
|
||||
import { baseProviderContextValue, useProviderContext } from '@/context/provider-context'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { ACCOUNT_SETTING_TAB } from '../constants'
|
||||
import AccountSetting from '../index'
|
||||
|
||||
let canReplaceLogo = true
|
||||
|
||||
const mockConsoleState = vi.hoisted(() => ({
|
||||
current: null as unknown,
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/context/provider-context')>()
|
||||
return {
|
||||
...actual,
|
||||
useProviderContext: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/context/workspace-state', async () => {
|
||||
const { createWorkspaceStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createWorkspaceStateModuleMock(() => mockConsoleState.current ?? {})
|
||||
@ -197,7 +190,10 @@ describe('AccountSetting', () => {
|
||||
}
|
||||
|
||||
return renderWithConsoleQuery(<StatefulAccountSetting />, {
|
||||
features: { billing: { subscription: { plan: 'sandbox' } } },
|
||||
features: {
|
||||
billing: { subscription: { plan: 'sandbox' } },
|
||||
can_replace_logo: canReplaceLogo,
|
||||
},
|
||||
accountProfile: (mockConsoleState.current as ConsoleStateFixture).userProfile,
|
||||
systemFeatures: {
|
||||
deployment_edition: deploymentEdition,
|
||||
@ -212,11 +208,7 @@ describe('AccountSetting', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(useProviderContext).mockReturnValue({
|
||||
...baseProviderContextValue,
|
||||
|
||||
enableReplaceWebAppLogo: true,
|
||||
})
|
||||
canReplaceLogo = true
|
||||
mockConsoleState.current = baseConsoleState
|
||||
vi.mocked(useBreakpoints).mockReturnValue(MediaType.pc)
|
||||
})
|
||||
@ -442,11 +434,7 @@ describe('AccountSetting', () => {
|
||||
|
||||
it('should hide billing and custom tabs when disabled', () => {
|
||||
// Arrange
|
||||
vi.mocked(useProviderContext).mockReturnValue({
|
||||
...baseProviderContextValue,
|
||||
|
||||
enableReplaceWebAppLogo: false,
|
||||
})
|
||||
canReplaceLogo = false
|
||||
|
||||
// Act
|
||||
renderAccountSetting({ deploymentEdition: 'COMMUNITY' })
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import type { AccountSettingTab } from '@/app/components/header/account-setting/constants'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
@ -8,7 +9,7 @@ import {
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -17,13 +18,13 @@ import CustomPage from '@/app/components/custom/custom-page'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import MenuDialog from '@/app/components/header/account-setting/menu-dialog'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
isCurrentWorkspaceDatasetOperatorAtom,
|
||||
isCurrentWorkspaceManagerAtom,
|
||||
} from '@/context/workspace-state'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import AccessRulesPage from './access-rules-page'
|
||||
import MembersPage from './members-page'
|
||||
@ -56,7 +57,11 @@ export default function AccountSetting({
|
||||
onTabChangeAction,
|
||||
}: IAccountSettingProps) {
|
||||
const { t } = useTranslation()
|
||||
const { enableReplaceWebAppLogo } = useProviderContext()
|
||||
const { data: enableReplaceWebAppLogo } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.can_replace_logo,
|
||||
}),
|
||||
)
|
||||
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const isCurrentWorkspaceManager = useAtomValue(isCurrentWorkspaceManagerAtom)
|
||||
|
||||
@ -6,8 +6,6 @@ import type { ConsoleStateFixture } from '@/test/console/state-fixture'
|
||||
import { screen, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { vi } from 'vite-plus/test'
|
||||
import { createMockProviderContextValue } from '@/__mocks__/provider-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { useUpdateRolesOfMember } from '@/service/access-control/use-member-roles'
|
||||
import { useMembers } from '@/service/use-common'
|
||||
@ -31,7 +29,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
return createPermissionStateModuleMock(() => mockConsoleState.current)
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context')
|
||||
vi.mock('@/hooks/use-format-time-from-now')
|
||||
vi.mock('@/service/access-control/use-member-roles')
|
||||
vi.mock('@/service/use-common')
|
||||
@ -247,11 +244,7 @@ describe('MembersPage', () => {
|
||||
} as unknown as ReturnType<typeof useUpdateRolesOfMember>)
|
||||
|
||||
deploymentEdition = 'COMMUNITY'
|
||||
vi.mocked(useProviderContext).mockReturnValue(
|
||||
createMockProviderContextValue({
|
||||
isAllowTransferWorkspace: true,
|
||||
}),
|
||||
)
|
||||
memberFeatures = { ...memberFeatures, is_allow_transfer_workspace: true }
|
||||
|
||||
vi.mocked(useFormatTimeFromNow).mockReturnValue({
|
||||
formatTimeFromNow: mockFormatTimeFromNow,
|
||||
@ -334,11 +327,7 @@ describe('MembersPage', () => {
|
||||
|
||||
it('should show non-interactive owner role when transfer ownership is not allowed', () => {
|
||||
deploymentEdition = 'COMMUNITY'
|
||||
vi.mocked(useProviderContext).mockReturnValue(
|
||||
createMockProviderContextValue({
|
||||
isAllowTransferWorkspace: false,
|
||||
}),
|
||||
)
|
||||
memberFeatures = { ...memberFeatures, is_allow_transfer_workspace: false }
|
||||
|
||||
renderMembersPage()
|
||||
|
||||
@ -406,7 +395,6 @@ describe('MembersPage', () => {
|
||||
billing: { subscription: { plan: 'sandbox' } },
|
||||
members: { size: 2, limit: 5 },
|
||||
}
|
||||
vi.mocked(useProviderContext).mockReturnValue(createMockProviderContextValue({}))
|
||||
|
||||
renderMembersPage()
|
||||
|
||||
@ -422,7 +410,6 @@ describe('MembersPage', () => {
|
||||
billing: { subscription: { plan: 'sandbox' } },
|
||||
members: { size: 2, limit: 0 },
|
||||
}
|
||||
vi.mocked(useProviderContext).mockReturnValue(createMockProviderContextValue({}))
|
||||
|
||||
renderMembersPage()
|
||||
|
||||
@ -435,7 +422,6 @@ describe('MembersPage', () => {
|
||||
billing: { subscription: { plan: 'team' } },
|
||||
members: { size: 2, limit: 50 },
|
||||
}
|
||||
vi.mocked(useProviderContext).mockReturnValue(createMockProviderContextValue({}))
|
||||
|
||||
renderMembersPage()
|
||||
|
||||
@ -540,7 +526,6 @@ describe('MembersPage', () => {
|
||||
billing: { subscription: { plan: 'sandbox' } },
|
||||
members: { size: 2, limit: 5 },
|
||||
}
|
||||
vi.mocked(useProviderContext).mockReturnValue(createMockProviderContextValue({}))
|
||||
|
||||
renderMembersPage()
|
||||
|
||||
@ -708,7 +693,6 @@ describe('MembersPage', () => {
|
||||
billing: { subscription: { plan: 'sandbox' } },
|
||||
members: { size: 2, limit: 2 },
|
||||
}
|
||||
vi.mocked(useProviderContext).mockReturnValue(createMockProviderContextValue({}))
|
||||
|
||||
renderMembersPage()
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@ import { WorkspaceAvatar } from '@/app/components/base/workspace-avatar'
|
||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { currentWorkspaceAtom, isCurrentWorkspaceOwnerAtom } from '@/context/workspace-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
@ -48,22 +47,24 @@ const MembersPage = () => {
|
||||
MemberInviteResponse['invitation_results'] | null
|
||||
>(null)
|
||||
const accounts = data?.accounts || []
|
||||
const { isAllowTransferWorkspace } = useProviderContext()
|
||||
const deploymentEdition = systemFeatures.deployment_edition
|
||||
const { data: billing } = useQuery(
|
||||
const { data: features } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
enabled: deploymentEdition === 'CLOUD',
|
||||
select: (data) => ({ plan: data.billing.subscription.plan, members: data.members }),
|
||||
select: (data) => ({
|
||||
plan: data.billing.subscription.plan,
|
||||
members: data.members,
|
||||
is_allow_transfer_workspace: data.is_allow_transfer_workspace,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
const isNotUnlimitedMemberPlan =
|
||||
deploymentEdition === 'CLOUD' && billing !== undefined && billing.plan !== 'team'
|
||||
deploymentEdition === 'CLOUD' && features !== undefined && features.plan !== 'team'
|
||||
// A limit of 0 means unlimited.
|
||||
const isMemberFull =
|
||||
isNotUnlimitedMemberPlan &&
|
||||
billing.members.limit > 0 &&
|
||||
accounts.length >= billing.members.limit
|
||||
features.members.limit > 0 &&
|
||||
accounts.length >= features.members.limit
|
||||
const [editWorkspaceModalVisible, setEditWorkspaceModalVisible] = useState(false)
|
||||
const [showTransferOwnershipModal, setShowTransferOwnershipModal] = useState(false)
|
||||
const [detailsMember, setDetailsMember] = useState<Member | null>(null)
|
||||
@ -151,9 +152,9 @@ const MembersPage = () => {
|
||||
<div className="">{accounts.length}</div>
|
||||
<div>/</div>
|
||||
<div>
|
||||
{billing.members.limit === 0
|
||||
{features.members.limit === 0
|
||||
? t(($) => $['plansCommon.unlimited'], { ns: 'billing' })
|
||||
: billing.members.limit}
|
||||
: features.members.limit}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@ -200,7 +201,9 @@ const MembersPage = () => {
|
||||
roles={account.roles}
|
||||
isCurrentUser={userProfileEmail === account.email}
|
||||
canManage={canManageMembers}
|
||||
canTransferOwnership={isCurrentWorkspaceOwner && isAllowTransferWorkspace}
|
||||
canTransferOwnership={
|
||||
isCurrentWorkspaceOwner && features?.is_allow_transfer_workspace === true
|
||||
}
|
||||
allowMultipleRoles={systemFeatures.rbac_enabled}
|
||||
onOpenDetails={handleOpenDetails}
|
||||
onTransferOwnership={handleTransferOwnership}
|
||||
|
||||
@ -14,7 +14,10 @@ let mockWorkspacePermissionKeys: string[] = ['plugin.model_config']
|
||||
function createWrapper(deploymentEdition: 'CLOUD' | 'COMMUNITY' = 'CLOUD') {
|
||||
return createConsoleQueryWrapper({
|
||||
systemFeatures: { deployment_edition: deploymentEdition },
|
||||
features: { billing: { subscription: { plan: mockPlanType } } },
|
||||
features: {
|
||||
billing: { subscription: { plan: mockPlanType } },
|
||||
model_load_balancing_enabled: mockModelLoadBalancingEnabled,
|
||||
},
|
||||
}).wrapper
|
||||
}
|
||||
|
||||
@ -25,10 +28,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: () => mockModelLoadBalancingEnabled,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/common', () => ({
|
||||
enableModel: vi.fn(),
|
||||
disableModel: vi.fn(),
|
||||
|
||||
@ -15,13 +15,10 @@ import ModelLoadBalancingConfigs from '../model-load-balancing-configs'
|
||||
|
||||
let mockModelLoadBalancingEnabled = true
|
||||
const render = (ui: React.ReactElement) =>
|
||||
renderWithConsoleQuery(ui, { systemFeatures: { deployment_edition: 'CLOUD' } })
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: (
|
||||
selector: (state: { modelLoadBalancingEnabled: boolean }) => boolean,
|
||||
) => selector({ modelLoadBalancingEnabled: mockModelLoadBalancingEnabled }),
|
||||
}))
|
||||
renderWithConsoleQuery(ui, {
|
||||
systemFeatures: { deployment_edition: 'CLOUD' },
|
||||
features: { model_load_balancing_enabled: mockModelLoadBalancingEnabled },
|
||||
})
|
||||
|
||||
vi.mock('../cooldown-timer', () => ({
|
||||
default: ({
|
||||
|
||||
@ -11,7 +11,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import { Balance } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { deploymentEditionAtom } from '@/features/system-features/state'
|
||||
import { disableModel, enableModel } from '@/service/common'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
@ -43,15 +42,14 @@ const ModelListItem = ({
|
||||
}: ModelListItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const deploymentEdition = useAtomValue(deploymentEditionAtom)
|
||||
const { data: plan } = useQuery(
|
||||
const { data: features } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
enabled: deploymentEdition === 'CLOUD',
|
||||
select: (features) => features.billing.subscription.plan,
|
||||
select: (features) => ({
|
||||
plan: features.billing.subscription.plan,
|
||||
model_load_balancing_enabled: features.model_load_balancing_enabled,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
const modelLoadBalancingEnabled = useProviderContextSelector(
|
||||
(state) => state.modelLoadBalancingEnabled,
|
||||
)
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canConfigureModels = hasPermission(workspacePermissionKeys, 'plugin.model_config')
|
||||
const queryClient = useQueryClient()
|
||||
@ -129,7 +127,7 @@ const ModelListItem = ({
|
||||
showFeaturesLabel
|
||||
></ModelName>
|
||||
<div className="flex shrink-0 items-center">
|
||||
{modelLoadBalancingEnabled &&
|
||||
{features?.model_load_balancing_enabled &&
|
||||
!model.deprecated &&
|
||||
model.load_balancing_enabled &&
|
||||
!model.has_invalid_load_balancing_configs && (
|
||||
@ -138,7 +136,9 @@ const ModelListItem = ({
|
||||
</Badge>
|
||||
)}
|
||||
{canConfigureModels &&
|
||||
(deploymentEdition !== 'CLOUD' || modelLoadBalancingEnabled || plan === 'sandbox') &&
|
||||
(deploymentEdition !== 'CLOUD' ||
|
||||
features?.model_load_balancing_enabled ||
|
||||
features?.plan === 'sandbox') &&
|
||||
!model.deprecated &&
|
||||
[ModelStatusEnum.active, ModelStatusEnum.disabled].includes(model.status) && (
|
||||
<ConfigModel
|
||||
|
||||
@ -12,7 +12,7 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge/index'
|
||||
@ -21,8 +21,8 @@ import { Infotip } from '@/app/components/base/infotip'
|
||||
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
|
||||
import s from '@/app/components/custom/style.module.css'
|
||||
import { AddCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { ConfigurationMethodEnum } from '../declarations'
|
||||
import CooldownTimer from './cooldown-timer'
|
||||
|
||||
@ -60,8 +60,10 @@ const ModelLoadBalancingConfigs = ({
|
||||
})
|
||||
const providerFormSchemaPredefined =
|
||||
configurationMethod === ConfigurationMethodEnum.predefinedModel
|
||||
const modelLoadBalancingEnabled = useProviderContextSelector(
|
||||
(state) => state.modelLoadBalancingEnabled,
|
||||
const { data: modelLoadBalancingEnabled } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.model_load_balancing_enabled,
|
||||
}),
|
||||
)
|
||||
|
||||
const updateConfigEntry = useCallback(
|
||||
@ -315,7 +317,7 @@ const ModelLoadBalancingConfigs = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!modelLoadBalancingEnabled && deploymentEdition === 'CLOUD' && (
|
||||
{modelLoadBalancingEnabled === false && deploymentEdition === 'CLOUD' && (
|
||||
<GridMask canvasClassName="rounded-xl!">
|
||||
<div className="mt-2 flex h-14 items-center justify-between rounded-xl border-[0.5px] border-components-panel-border px-4 shadow-md">
|
||||
<div className={cn('text-gradient text-sm/tight font-semibold', s.textGradient)}>
|
||||
|
||||
@ -12,7 +12,6 @@ import type { ReactNode } from 'react'
|
||||
import type { Mock } from 'vite-plus/test'
|
||||
import type { StepByStepTourSessionState } from '@/app/components/step-by-step-tour/types'
|
||||
import type { ModalContextState } from '@/context/modal-context'
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
import type { UserProfileWithMeta } from '@/features/account-profile/client'
|
||||
import type { ConsoleStateFixture } from '@/test/console/state-fixture'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
@ -30,7 +29,6 @@ import {
|
||||
} from '@/app/components/step-by-step-tour/state'
|
||||
import { STEP_BY_STEP_TOUR_SHELL_MODE_STORAGE_KEY } from '@/app/components/step-by-step-tour/storage'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { usePathname, useRouter } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
@ -168,11 +166,8 @@ type MainNavConsoleState = ConsoleStateFixture & {
|
||||
const mockConsoleState = vi.hoisted(() => ({
|
||||
current: undefined as MainNavConsoleState | undefined,
|
||||
}))
|
||||
const mockProviderContextState = vi.hoisted(() => ({
|
||||
current: {
|
||||
enableSkill: true,
|
||||
} as Partial<ProviderContextState>,
|
||||
}))
|
||||
let skillEnabled = true
|
||||
let educationEnabled = false
|
||||
|
||||
vi.mock('@tanstack/react-virtual')
|
||||
|
||||
@ -192,12 +187,6 @@ vi.mock('@/context/permission-state', async () => {
|
||||
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createPermissionStateModuleMock(() => mockConsoleState.current ?? {})
|
||||
})
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
useProviderContextSelector: vi.fn((selector: (state: Partial<ProviderContextState>) => unknown) =>
|
||||
selector(mockProviderContextState.current),
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: vi.fn(),
|
||||
@ -603,7 +592,11 @@ const renderMainNav = (
|
||||
</JotaiProvider>,
|
||||
{
|
||||
systemFeatures: resolvedSystemFeatures,
|
||||
features: { billing: { subscription: { plan: 'sandbox' } } },
|
||||
features: {
|
||||
billing: { subscription: { plan: 'sandbox' } },
|
||||
enable_skill: skillEnabled,
|
||||
education: { enabled: educationEnabled },
|
||||
},
|
||||
educationStatus: options.educationStatus,
|
||||
workspacePermissionKeys: currentConsoleState.workspacePermissionKeys,
|
||||
queryClient,
|
||||
@ -651,12 +644,8 @@ describe('MainNav', () => {
|
||||
refresh: vi.fn(),
|
||||
})
|
||||
mockConsoleState.current = consoleState
|
||||
mockProviderContextState.current = {
|
||||
enableSkill: true,
|
||||
}
|
||||
;(useProviderContext as Mock).mockReturnValue({
|
||||
enableEducationPlan: false,
|
||||
} as ProviderContextState)
|
||||
skillEnabled = true
|
||||
educationEnabled = false
|
||||
;(useModalContext as Mock).mockReturnValue({
|
||||
setShowPricingModal: mockSetShowPricingModal,
|
||||
} as unknown as ModalContextState)
|
||||
@ -775,9 +764,7 @@ describe('MainNav', () => {
|
||||
})
|
||||
|
||||
it('hides the skills entry when skill is disabled', () => {
|
||||
mockProviderContextState.current = {
|
||||
enableSkill: false,
|
||||
}
|
||||
skillEnabled = false
|
||||
|
||||
renderMainNav()
|
||||
|
||||
@ -831,9 +818,7 @@ describe('MainNav', () => {
|
||||
})
|
||||
|
||||
it('shows the user education badge in the account popup without adding the workspace plan there', async () => {
|
||||
;(useProviderContext as Mock).mockReturnValue({
|
||||
enableEducationPlan: true,
|
||||
} as ProviderContextState)
|
||||
educationEnabled = true
|
||||
|
||||
renderMainNav(defaultMainNavSystemFeatures, {
|
||||
educationStatus: { is_student: true },
|
||||
|
||||
@ -3,13 +3,11 @@ import type {
|
||||
TenantListItemResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ModalContextState } from '@/context/modal-context'
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
import { zLicenseStatus } from '@dify/contracts/api/console/system-features/zod.gen'
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import {
|
||||
createConsoleQueryClient,
|
||||
@ -35,10 +33,6 @@ const mockConsoleState = vi.hoisted(() => ({
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/permission-state', async () => {
|
||||
const { createPermissionStateModuleMock } = await import('@/test/console/state-fixture')
|
||||
return createPermissionStateModuleMock(() => mockConsoleState.current)
|
||||
@ -176,9 +170,6 @@ describe('WorkspaceCard', () => {
|
||||
mockFetchWorkspaces.mockResolvedValue({ workspaces: mockWorkspaces })
|
||||
mockSwitchWorkspace.mockReturnValue(new Promise(() => {}))
|
||||
mockCurrentWorkspaceQuery()
|
||||
vi.mocked(useProviderContext).mockReturnValue({
|
||||
enableEducationPlan: false,
|
||||
} as ProviderContextState)
|
||||
mockWorkspacePermissionKeys(['workspace.member.manage'])
|
||||
vi.mocked(useModalContext).mockReturnValue({
|
||||
setShowPricingModal: mockSetShowPricingModal,
|
||||
@ -340,9 +331,6 @@ describe('WorkspaceCard', () => {
|
||||
...currentWorkspaceValue,
|
||||
plan: 'team',
|
||||
})
|
||||
vi.mocked(useProviderContext).mockReturnValue({
|
||||
enableEducationPlan: false,
|
||||
} as ProviderContextState)
|
||||
renderWorkspaceCard({ systemFeatures: { deployment_edition: 'CLOUD' } })
|
||||
|
||||
expect(screen.getByText('team')).toBeInTheDocument()
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import type { MainNavItem, MainNavProps } from './types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -10,7 +10,6 @@ import Badge from '@/app/components/base/badge'
|
||||
import { DifyLogo } from '@/app/components/base/logo/dify-logo'
|
||||
import EnvNav from '@/app/components/header/env-nav'
|
||||
import StepByStepTourMount from '@/app/components/step-by-step-tour/mount'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { isCurrentWorkspaceDatasetOperatorAtom } from '@/context/workspace-state'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag'
|
||||
@ -20,6 +19,7 @@ import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import Link from '@/next/link'
|
||||
import { usePathname } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import AccountSection from './components/account-section'
|
||||
import HelpMenu from './components/help-menu'
|
||||
import MainNavLink from './components/nav-link'
|
||||
@ -41,7 +41,11 @@ export function MainNav({ className }: MainNavProps) {
|
||||
const agentV2Enabled = isAgentV2Enabled()
|
||||
const canManageAgents = useCanManageAgents()
|
||||
const canViewSkills = useCanViewSkills()
|
||||
const enableSkill = useProviderContextSelector((state) => state.enableSkill)
|
||||
const { data: enableSkill } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.enable_skill,
|
||||
}),
|
||||
)
|
||||
const showEnvTag = currentEnv === 'TESTING' || currentEnv === 'DEVELOPMENT'
|
||||
const helpMenuTriggerRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
@ -54,7 +58,7 @@ export function MainNav({ className }: MainNavProps) {
|
||||
canViewSkills,
|
||||
isCurrentWorkspaceDatasetOperator,
|
||||
marketplaceEnabled: systemFeatures.enable_marketplace,
|
||||
skillEnabled: enableSkill,
|
||||
skillEnabled: enableSkill === true,
|
||||
}),
|
||||
).map((route) => ({
|
||||
href: route.href,
|
||||
|
||||
@ -4,7 +4,7 @@ import { act, fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { seedAccountProfileQuery } from '@/test/console/account-profile'
|
||||
import { seedSystemFeatures } from '@/test/console/query-data'
|
||||
import { seedFeatures, seedSystemFeatures } from '@/test/console/query-data'
|
||||
import { render } from '@/test/console/render'
|
||||
import Publisher from '../index'
|
||||
import { Popup } from '../popup'
|
||||
@ -126,20 +126,7 @@ vi.mock('@/context/modal-context', () => ({
|
||||
): T => selector({ setShowPricingModal: mockSetShowPricingModal }),
|
||||
}))
|
||||
|
||||
const mockIsAllowPublishAsCustomKnowledgePipelineTemplate = vi.fn(() => true)
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate:
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate(),
|
||||
}),
|
||||
useProviderContextSelector: <T,>(
|
||||
selector: (s: { isAllowPublishAsCustomKnowledgePipelineTemplate: boolean }) => T,
|
||||
): T =>
|
||||
selector({
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate:
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate(),
|
||||
}),
|
||||
}))
|
||||
let publishEnabled = true
|
||||
|
||||
const toastMocks = vi.hoisted(() => ({
|
||||
call: vi.fn(),
|
||||
@ -246,6 +233,7 @@ const createQueryClient = () =>
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
staleTime: Infinity,
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -254,6 +242,7 @@ const renderWithQueryClient = (ui: React.ReactElement) => {
|
||||
const queryClient = createQueryClient()
|
||||
seedAccountProfileQuery(queryClient, { id: 'user-1' })
|
||||
seedSystemFeatures(queryClient, { deployment_edition: 'CLOUD' })
|
||||
seedFeatures(queryClient, { knowledge_pipeline: { publish_enabled: publishEnabled } })
|
||||
return render(<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>)
|
||||
}
|
||||
|
||||
@ -265,7 +254,7 @@ describe('publisher', () => {
|
||||
mockPublishedAt.mockReturnValue(null)
|
||||
mockDraftUpdatedAt.mockReturnValue(1700000000)
|
||||
mockPipelineId.mockReturnValue('test-pipeline-id')
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(true)
|
||||
publishEnabled = true
|
||||
mockHandleCheckBeforePublish.mockResolvedValue(true)
|
||||
mockDatasetPermissionKeys = ['dataset.acl.use']
|
||||
mockDatasetMaintainer = undefined
|
||||
@ -360,7 +349,7 @@ describe('publisher', () => {
|
||||
|
||||
it('should close the outer popover before opening publish-as follow-up flow', async () => {
|
||||
mockPublishedAt.mockReturnValue(1700000000)
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(false)
|
||||
publishEnabled = false
|
||||
renderWithQueryClient(<Publisher />)
|
||||
|
||||
fireEvent.click(screen.getByText('workflow.common.publish'))
|
||||
@ -429,7 +418,7 @@ describe('publisher', () => {
|
||||
|
||||
it('should show premium badge when publish as template is not allowed', () => {
|
||||
mockPublishedAt.mockReturnValue(1700000000)
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(false)
|
||||
publishEnabled = false
|
||||
|
||||
renderWithQueryClient(<Popup />)
|
||||
|
||||
@ -438,7 +427,7 @@ describe('publisher', () => {
|
||||
|
||||
it('should not show premium badge when publish as template is allowed', () => {
|
||||
mockPublishedAt.mockReturnValue(1700000000)
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(true)
|
||||
publishEnabled = true
|
||||
|
||||
renderWithQueryClient(<Popup />)
|
||||
|
||||
@ -500,7 +489,7 @@ describe('publisher', () => {
|
||||
|
||||
it('should show pricing modal when publish as template is clicked without permission', async () => {
|
||||
mockPublishedAt.mockReturnValue(1700000000)
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(false)
|
||||
publishEnabled = false
|
||||
renderWithQueryClient(<Popup />)
|
||||
|
||||
const publishAsButton = screen
|
||||
@ -513,7 +502,7 @@ describe('publisher', () => {
|
||||
|
||||
it('should show publish as knowledge pipeline modal when permitted', async () => {
|
||||
mockPublishedAt.mockReturnValue(1700000000)
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(true)
|
||||
publishEnabled = true
|
||||
renderWithQueryClient(<Publisher />)
|
||||
|
||||
fireEvent.click(screen.getByText('workflow.common.publish'))
|
||||
@ -530,7 +519,7 @@ describe('publisher', () => {
|
||||
|
||||
it('should close publish as knowledge pipeline modal when cancel is clicked', async () => {
|
||||
mockPublishedAt.mockReturnValue(1700000000)
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(true)
|
||||
publishEnabled = true
|
||||
renderWithQueryClient(<Publisher />)
|
||||
|
||||
fireEvent.click(screen.getByText('workflow.common.publish'))
|
||||
@ -851,7 +840,7 @@ describe('publisher', () => {
|
||||
|
||||
describe('Prop Variations', () => {
|
||||
it('should display correct width when permission is allowed', () => {
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(true)
|
||||
publishEnabled = true
|
||||
const { container } = renderWithQueryClient(<Popup />)
|
||||
|
||||
const popupDiv = container.firstChild as HTMLElement
|
||||
@ -859,7 +848,7 @@ describe('publisher', () => {
|
||||
})
|
||||
|
||||
it('should display correct width when permission is not allowed', () => {
|
||||
mockIsAllowPublishAsCustomKnowledgePipelineTemplate.mockReturnValue(false)
|
||||
publishEnabled = false
|
||||
const { container } = renderWithQueryClient(<Popup />)
|
||||
|
||||
const popupDiv = container.firstChild as HTMLElement
|
||||
|
||||
@ -4,9 +4,12 @@ import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
||||
import { render as renderWithConsoleState } from '@/test/console/render'
|
||||
import { Popup } from '../popup'
|
||||
|
||||
let mockIsAllowPublishAsCustom = true
|
||||
|
||||
const render = (ui: React.ReactElement) => {
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
systemFeatures: { deployment_edition: 'CLOUD' },
|
||||
features: { knowledge_pipeline: { publish_enabled: mockIsAllowPublishAsCustom } },
|
||||
})
|
||||
return renderWithConsoleState(ui, { wrapper })
|
||||
}
|
||||
@ -50,7 +53,6 @@ const mockInvalidCustomizedTemplateList = vi.fn()
|
||||
let mockPublishedAt: string | undefined = '2024-01-01T00:00:00Z'
|
||||
let mockDraftUpdatedAt: string | undefined = '2024-06-01T00:00:00Z'
|
||||
let mockPipelineId: string | undefined = 'pipeline-123'
|
||||
let mockIsAllowPublishAsCustom = true
|
||||
let mockDatasetPermissionKeys = ['dataset.acl.use']
|
||||
let mockDatasetMaintainer: string | undefined
|
||||
let mockCurrentUserId = 'user-1'
|
||||
@ -154,10 +156,6 @@ vi.mock('@/context/modal-context', () => ({
|
||||
) => selector({ setShowPricingModal: mockSetShowPricingModal }),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: () => mockIsAllowPublishAsCustom,
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-api-access-url', () => ({
|
||||
useDatasetApiAccessUrl: () => '/api/datasets/ds-123',
|
||||
}))
|
||||
|
||||
@ -14,7 +14,7 @@ import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { RiArrowRightUpLine, RiPlayCircleLine, RiTerminalBoxLine } from '@remixicon/react'
|
||||
import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useState } from 'react'
|
||||
@ -31,13 +31,13 @@ import {
|
||||
workspacePermissionKeysAtom,
|
||||
workspacePermissionKeysLoadingAtom,
|
||||
} from '@/context/permission-state'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import Link from '@/next/link'
|
||||
import { useParams } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
|
||||
import { useInvalid } from '@/service/use-base'
|
||||
import { publishedPipelineInfoQueryKeyPrefix } from '@/service/use-pipeline'
|
||||
@ -84,8 +84,10 @@ export function Popup({
|
||||
const { handleCheckBeforePublish } = useChecklistBeforePublish()
|
||||
const { mutateAsync: publishWorkflow } = usePublishWorkflow()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const isAllowPublishAsCustomKnowledgePipelineTemplate = useProviderContextSelector(
|
||||
(s) => s.isAllowPublishAsCustomKnowledgePipelineTemplate,
|
||||
const { data: isAllowPublishAsCustomKnowledgePipelineTemplate } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.knowledge_pipeline.publish_enabled,
|
||||
}),
|
||||
)
|
||||
const setShowPricingModal = useModalContextSelector((s) => s.setShowPricingModal)
|
||||
const apiReferenceUrl = useDatasetApiAccessUrl()
|
||||
@ -195,6 +197,8 @@ export function Popup({
|
||||
preventDefault: true,
|
||||
})
|
||||
const handleClickPublishAsKnowledgePipeline = useCallback(() => {
|
||||
if (isAllowPublishAsCustomKnowledgePipelineTemplate === undefined) return
|
||||
|
||||
onRequestClose?.()
|
||||
if (!isAllowPublishAsCustomKnowledgePipelineTemplate) {
|
||||
if (deploymentEdition === 'CLOUD') setShowPricingModal()
|
||||
@ -318,7 +322,11 @@ export function Popup({
|
||||
className="w-full hover:bg-state-accent-hover hover:text-text-accent"
|
||||
variant="tertiary"
|
||||
onClick={handleClickPublishAsKnowledgePipeline}
|
||||
disabled={!publishedAt || isPublishingAsCustomizedPipeline}
|
||||
disabled={
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate === undefined ||
|
||||
!publishedAt ||
|
||||
isPublishingAsCustomizedPipeline
|
||||
}
|
||||
>
|
||||
<div className="flex grow items-center gap-x-2 overflow-hidden">
|
||||
<span aria-hidden className="i-custom-vender-pipeline-pipeline-line size-4 shrink-0" />
|
||||
@ -328,17 +336,18 @@ export function Popup({
|
||||
>
|
||||
{t(($) => $['common.publishAs'], { ns: 'pipeline' })}
|
||||
</span>
|
||||
{deploymentEdition === 'CLOUD' && !isAllowPublishAsCustomKnowledgePipelineTemplate && (
|
||||
<PremiumBadge className="shrink-0 select-none" size="s" color="indigo">
|
||||
<SparklesSoft
|
||||
aria-hidden="true"
|
||||
className="flex size-3 items-center text-components-premium-badge-indigo-text-stop-0"
|
||||
/>
|
||||
<span className="p-0.5 system-2xs-medium">
|
||||
{t(($) => $['upgradeBtn.encourageShort'], { ns: 'billing' })}
|
||||
</span>
|
||||
</PremiumBadge>
|
||||
)}
|
||||
{deploymentEdition === 'CLOUD' &&
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate === false && (
|
||||
<PremiumBadge className="shrink-0 select-none" size="s" color="indigo">
|
||||
<SparklesSoft
|
||||
aria-hidden="true"
|
||||
className="flex size-3 items-center text-components-premium-badge-indigo-text-stop-0"
|
||||
/>
|
||||
<span className="p-0.5 system-2xs-medium">
|
||||
{t(($) => $['upgradeBtn.encourageShort'], { ns: 'billing' })}
|
||||
</span>
|
||||
</PremiumBadge>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -5,12 +5,16 @@ import { renderWithConsoleQuery } from '@/test/console/query-data'
|
||||
import { DeliveryMethodType } from '../../../types'
|
||||
import MethodSelector from '../method-selector'
|
||||
|
||||
let emailDeliveryEnabled = true
|
||||
|
||||
const render = (ui: React.ReactElement) =>
|
||||
renderWithConsoleQuery(ui, { systemFeatures: { deployment_edition: 'CLOUD' } })
|
||||
renderWithConsoleQuery(ui, {
|
||||
systemFeatures: { deployment_edition: 'CLOUD' },
|
||||
features: { human_input_email_delivery_enabled: emailDeliveryEnabled },
|
||||
})
|
||||
|
||||
const mockUuid = vi.hoisted(() => vi.fn())
|
||||
const mockUseWorkflowNodes = vi.hoisted(() => vi.fn())
|
||||
const mockUseProviderContextSelector = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('uuid', () => ({
|
||||
v4: () => mockUuid(),
|
||||
@ -21,12 +25,6 @@ vi.mock('@/app/components/workflow/store/workflow/use-nodes', () => ({
|
||||
default: () => mockUseWorkflowNodes(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: (
|
||||
selector: (state: { humanInputEmailDeliveryEnabled: boolean }) => boolean,
|
||||
) => mockUseProviderContextSelector(selector),
|
||||
}))
|
||||
|
||||
describe('human-input/delivery-method/method-selector', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@ -37,11 +35,7 @@ describe('human-input/delivery-method/method-selector', () => {
|
||||
data: { type: BlockEnum.Start },
|
||||
},
|
||||
] as Node[])
|
||||
mockUseProviderContextSelector.mockImplementation((selector) =>
|
||||
selector({
|
||||
humanInputEmailDeliveryEnabled: true,
|
||||
}),
|
||||
)
|
||||
emailDeliveryEnabled = true
|
||||
})
|
||||
|
||||
it('should add webapp and email delivery methods when both entries are available', () => {
|
||||
@ -114,11 +108,7 @@ describe('human-input/delivery-method/method-selector', () => {
|
||||
data: { type: BlockEnum.TriggerSchedule },
|
||||
},
|
||||
] as Node[])
|
||||
mockUseProviderContextSelector.mockImplementation((selector) =>
|
||||
selector({
|
||||
humanInputEmailDeliveryEnabled: false,
|
||||
}),
|
||||
)
|
||||
emailDeliveryEnabled = false
|
||||
|
||||
render(<MethodSelector data={[]} onAdd={handleAdd} onShowUpgradeTip={handleShowUpgradeTip} />)
|
||||
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type { DeliveryMethod } from '../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { memo, useMemo, useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import useWorkflowNodes from '@/app/components/workflow/store/workflow/use-nodes'
|
||||
import { isTriggerWorkflow } from '@/app/components/workflow/utils/workflow-entry'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { DeliveryMethodType } from '../../types'
|
||||
|
||||
const i18nPrefix = 'nodes.humanInput'
|
||||
@ -30,8 +31,10 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
|
||||
select: ({ deployment_edition }) => deployment_edition,
|
||||
})
|
||||
const [open, setOpen] = useState(false)
|
||||
const humanInputEmailDeliveryEnabled = useProviderContextSelector(
|
||||
(s) => s.humanInputEmailDeliveryEnabled,
|
||||
const { data: humanInputEmailDeliveryEnabled } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.human_input_email_delivery_enabled,
|
||||
}),
|
||||
)
|
||||
const nodes = useWorkflowNodes()
|
||||
|
||||
@ -46,7 +49,7 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
|
||||
|
||||
const emailDeliveryInfo = useMemo(() => {
|
||||
return {
|
||||
noPermission: !humanInputEmailDeliveryEnabled,
|
||||
noPermission: humanInputEmailDeliveryEnabled === false,
|
||||
added: data.some((method) => method.type === DeliveryMethodType.Email),
|
||||
}
|
||||
}, [data, humanInputEmailDeliveryEnabled])
|
||||
@ -124,9 +127,11 @@ const MethodSelector: FC<MethodSelectorProps> = ({ data, onAdd, onShowUpgradeTip
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex cursor-pointer items-center gap-1 rounded-lg p-1 pl-3 hover:bg-state-base-hover',
|
||||
emailDeliveryInfo.added && 'cursor-not-allowed bg-transparent hover:bg-transparent',
|
||||
(emailDeliveryInfo.added || humanInputEmailDeliveryEnabled === undefined) &&
|
||||
'cursor-not-allowed bg-transparent hover:bg-transparent',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (humanInputEmailDeliveryEnabled === undefined) return
|
||||
if (emailDeliveryInfo.noPermission) {
|
||||
onShowUpgradeTip()
|
||||
return
|
||||
|
||||
@ -11,12 +11,6 @@ const mockEducationStatus = vi.hoisted(() => ({
|
||||
}))
|
||||
const mockPricingModal = vi.hoisted(() => ({ isOpen: false }))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
enableEducationPlan: true,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-query-params', () => ({
|
||||
usePricingModal: () => [mockPricingModal.isOpen, vi.fn()],
|
||||
}))
|
||||
@ -37,6 +31,7 @@ vi.mock('@/next/dynamic', () => ({
|
||||
const renderNotice = (accountId = 'user-1') => {
|
||||
const { wrapper } = createConsoleQueryWrapper({
|
||||
accountProfile: { id: accountId, timezone: 'UTC' },
|
||||
features: { education: { enabled: true } },
|
||||
educationStatus: {
|
||||
allow_refresh: mockEducationStatus.allowRefresh,
|
||||
expire_at: mockEducationStatus.expireAt,
|
||||
|
||||
@ -6,7 +6,6 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import dayjs from 'dayjs'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { useDismissedEducationExpireNotice } from './storage'
|
||||
@ -74,10 +73,14 @@ export function useEducationExpireNotice() {
|
||||
timezone: profile.timezone ?? undefined,
|
||||
}),
|
||||
})
|
||||
const { enableEducationPlan } = useProviderContext()
|
||||
const { data: enableEducationPlan } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.education.enabled,
|
||||
}),
|
||||
)
|
||||
const { data: educationStatus, isLoading: isLoadingEducationStatus } = useQuery(
|
||||
consoleQuery.account.education.get.queryOptions({
|
||||
enabled: enableEducationPlan,
|
||||
enabled: enableEducationPlan === true,
|
||||
select: selectEducationExpireStatus,
|
||||
}),
|
||||
)
|
||||
|
||||
@ -16,7 +16,6 @@ type ProviderContextProviderProps = {
|
||||
|
||||
export const ProviderContextProvider = ({ children }: ProviderContextProviderProps) => {
|
||||
const queryClient = useQueryClient()
|
||||
const featuresQuery = useQuery(consoleQuery.features.get.queryOptions())
|
||||
const {
|
||||
data: providersData,
|
||||
isLoading: isLoadingModelProviders,
|
||||
@ -24,16 +23,6 @@ export const ProviderContextProvider = ({ children }: ProviderContextProviderPro
|
||||
} = useQuery(consoleQuery.workspaces.current.modelProviders.summary.get.queryOptions())
|
||||
const { data: textGenerationModelList } = useModelListByType(ModelTypeEnum.textGeneration)
|
||||
|
||||
const features = featuresQuery.data
|
||||
const enableEducationPlan = features?.education.enabled ?? false
|
||||
const enableSkill = features?.enable_skill ?? false
|
||||
const enableReplaceWebAppLogo = features?.can_replace_logo ?? false
|
||||
const modelLoadBalancingEnabled = features?.model_load_balancing_enabled ?? false
|
||||
const isAllowTransferWorkspace = features?.is_allow_transfer_workspace ?? false
|
||||
const isAllowPublishAsCustomKnowledgePipelineTemplate =
|
||||
features?.knowledge_pipeline.publish_enabled ?? false
|
||||
const humanInputEmailDeliveryEnabled = features?.human_input_email_delivery_enabled ?? false
|
||||
|
||||
const refreshModelProviders = () =>
|
||||
Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
@ -54,13 +43,6 @@ export const ProviderContextProvider = ({ children }: ProviderContextProviderPro
|
||||
isAPIKeySet: !!textGenerationModelList?.data?.some(
|
||||
(model) => model.status === ModelStatusEnum.active,
|
||||
),
|
||||
enableSkill,
|
||||
enableReplaceWebAppLogo,
|
||||
modelLoadBalancingEnabled,
|
||||
enableEducationPlan,
|
||||
isAllowTransferWorkspace,
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate,
|
||||
humanInputEmailDeliveryEnabled,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -15,16 +15,9 @@ export type ProviderContextState = {
|
||||
refreshModelProviders: () => Promise<void>
|
||||
textGenerationModelList: Model[]
|
||||
isAPIKeySet: boolean
|
||||
enableSkill: boolean
|
||||
enableReplaceWebAppLogo: boolean
|
||||
modelLoadBalancingEnabled: boolean
|
||||
enableEducationPlan: boolean
|
||||
isAllowTransferWorkspace: boolean
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate: boolean
|
||||
humanInputEmailDeliveryEnabled: boolean
|
||||
}
|
||||
|
||||
export const baseProviderContextValue: ProviderContextState = {
|
||||
const baseProviderContextValue: ProviderContextState = {
|
||||
modelProviders: [],
|
||||
modelProviderPlugins: {},
|
||||
isLoadingModelProviders: false,
|
||||
@ -32,13 +25,6 @@ export const baseProviderContextValue: ProviderContextState = {
|
||||
refreshModelProviders: async () => {},
|
||||
textGenerationModelList: [],
|
||||
isAPIKeySet: true,
|
||||
enableSkill: false,
|
||||
enableReplaceWebAppLogo: false,
|
||||
modelLoadBalancingEnabled: false,
|
||||
enableEducationPlan: false,
|
||||
isAllowTransferWorkspace: false,
|
||||
isAllowPublishAsCustomKnowledgePipelineTemplate: false,
|
||||
humanInputEmailDeliveryEnabled: false,
|
||||
}
|
||||
|
||||
export const ProviderContext = createContext<ProviderContextState>(baseProviderContextValue)
|
||||
|
||||
@ -10,11 +10,17 @@ import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store
|
||||
import { agentComposerKnowledgeRetrievalsAtom } from '@/features/agent-v2/agent-composer/store-modules/knowledge'
|
||||
import { agentComposerPromptAtom } from '@/features/agent-v2/agent-composer/store-modules/prompt'
|
||||
import { agentComposerToolsAtom } from '@/features/agent-v2/agent-composer/store-modules/tools'
|
||||
import { render } from '@/test/console/render'
|
||||
import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
||||
import { render as renderWithState } from '@/test/console/render'
|
||||
import { seedRegisteredConsoleStateFixture } from '@/test/console/state-fixture'
|
||||
import { AgentPromptEditor } from '../orchestrate/prompt-editor'
|
||||
import { AgentPromptSlashMenu } from '../orchestrate/prompt-editor/slash'
|
||||
|
||||
const render = (ui: React.ReactElement) => {
|
||||
const { wrapper } = createConsoleQueryWrapper({ features: { enable_skill: true } })
|
||||
return renderWithState(ui, { wrapper })
|
||||
}
|
||||
|
||||
const mockPromptEditor = vi.hoisted(() => vi.fn())
|
||||
const mockCopy = vi.hoisted(() => vi.fn())
|
||||
const mockReset = vi.hoisted(() => vi.fn())
|
||||
@ -199,11 +205,6 @@ vi.mock('@/context/workspace-state', async () => {
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: (selector: (state: { enableSkill: boolean }) => unknown) =>
|
||||
selector({ enableSkill: true }),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useAllBuiltInTools: () => ({ data: mockBuiltInTools }),
|
||||
useAllCustomTools: () => ({ data: [] }),
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { createContext, use } from 'react'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { agentComposerFilesAtom } from '@/features/agent-v2/agent-composer/store-modules/files'
|
||||
import { agentComposerSkillsAtom } from '@/features/agent-v2/agent-composer/store-modules/skills'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
@ -41,7 +40,11 @@ export const useAgentConfigSkills = () => {
|
||||
|
||||
export const useAgentWorkspaceSkillBindings = () => {
|
||||
const { agentId } = useAgentConfigApiContext()
|
||||
const enableSkill = useProviderContextSelector((state) => state.enableSkill)
|
||||
const { data: enableSkill } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.enable_skill,
|
||||
}),
|
||||
)
|
||||
|
||||
return useQuery({
|
||||
...consoleQuery.workspaces.current.agents.byAgentId.skills.get.queryOptions({
|
||||
@ -51,7 +54,7 @@ export const useAgentWorkspaceSkillBindings = () => {
|
||||
},
|
||||
},
|
||||
}),
|
||||
enabled: enableSkill,
|
||||
enabled: enableSkill === true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useClipboard } from 'foxact/use-clipboard'
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||
import {
|
||||
@ -38,7 +39,6 @@ import { Infotip } from '@/app/components/base/infotip'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import { agentComposerKnowledgeRetrievalsAtom } from '@/features/agent-v2/agent-composer/store-modules/knowledge'
|
||||
import { agentComposerPromptAtom } from '@/features/agent-v2/agent-composer/store-modules/prompt'
|
||||
import {
|
||||
@ -49,6 +49,7 @@ import {
|
||||
ENABLE_AGENT_CLI_TOOLS,
|
||||
ENABLE_AGENT_KNOWLEDGE_RETRIEVAL,
|
||||
} from '@/features/agent-v2/agent-detail/configure/feature-flags'
|
||||
import { consoleQuery } from '@/service/console'
|
||||
import { useAgentOrchestrateAddActions } from '../add-actions-context'
|
||||
import { AgentConfigureTipContent } from '../common/tip-content'
|
||||
import {
|
||||
@ -422,7 +423,11 @@ function AgentPromptSelectionBridge({
|
||||
export function AgentPromptEditor() {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const readOnly = useAgentOrchestrateReadOnly()
|
||||
const enableSkill = useProviderContextSelector((state) => state.enableSkill)
|
||||
const { data: enableSkill } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.enable_skill,
|
||||
}),
|
||||
)
|
||||
const [value, setValue] = useAtom(agentComposerPromptAtom)
|
||||
const { skills: embeddedSkills } = useAgentConfigSkills()
|
||||
const workspaceSkillBindingsQuery = useAgentWorkspaceSkillBindings()
|
||||
@ -1060,7 +1065,7 @@ export function AgentPromptEditor() {
|
||||
onAddFile={addActions.files}
|
||||
onAddKnowledge={addActions.knowledge}
|
||||
onAddSkill={addActions.skills}
|
||||
canAddWorkspaceSkill={enableSkill}
|
||||
canAddWorkspaceSkill={enableSkill === true}
|
||||
knowledgeRetrievals={retrievals}
|
||||
onBack={returnToSlashMenuMain}
|
||||
onOpenCategory={handleOpenSlashMenuCategory}
|
||||
|
||||
@ -12,6 +12,7 @@ import { formStateToAgentSoulConfig } from '@/features/agent-v2/agent-composer/c
|
||||
import { defaultAgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider'
|
||||
import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
|
||||
import { seedFeatures } from '@/test/console/query-data'
|
||||
import { AgentOrchestrateAddActionsProvider } from '../../add-actions'
|
||||
import { useAgentOrchestrateAddActions } from '../../add-actions-context'
|
||||
import { AgentConfigApiContextProvider } from '../../config-context'
|
||||
@ -81,9 +82,7 @@ const mocks = vi.hoisted(() => ({
|
||||
fileUploadConfig: {
|
||||
skill_file_size_limit: 64,
|
||||
},
|
||||
providerContext: {
|
||||
enableSkill: true,
|
||||
},
|
||||
skillEnabled: true,
|
||||
}))
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
@ -115,13 +114,9 @@ vi.mock('@/context/permission-state', async () => {
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContextSelector: (selector: (state: { enableSkill: boolean }) => unknown) =>
|
||||
selector(mocks.providerContext),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/console', () => ({
|
||||
vi.mock('@/service/console', async (importOriginal) => ({
|
||||
consoleQuery: {
|
||||
features: (await importOriginal<typeof import('@/service/console')>()).consoleQuery.features,
|
||||
tags: {
|
||||
get: {
|
||||
queryOptions: mocks.workspaceSkillTagsQueryOptions,
|
||||
@ -224,7 +219,9 @@ vi.mock('@/service/console', () => ({
|
||||
queryOptions: mocks.agentSkillBindingsQueryOptions,
|
||||
},
|
||||
put: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.replaceAgentSkillBindingsMutationFn }),
|
||||
mutationOptions: () => ({
|
||||
mutationFn: mocks.replaceAgentSkillBindingsMutationFn,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -328,6 +325,8 @@ function renderAgentSkills({
|
||||
},
|
||||
})
|
||||
|
||||
seedFeatures(queryClient, { enable_skill: mocks.skillEnabled })
|
||||
|
||||
return {
|
||||
...render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
@ -353,7 +352,7 @@ function renderAgentSkills({
|
||||
describe('AgentSkills', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mocks.providerContext.enableSkill = true
|
||||
mocks.skillEnabled = true
|
||||
mocks.fileUploadConfig.skill_file_size_limit = 64
|
||||
vi.stubGlobal('fetch', mocks.fetch)
|
||||
document.cookie = 'csrf_token=csrf-token; path=/'
|
||||
@ -790,7 +789,7 @@ describe('AgentSkills', () => {
|
||||
|
||||
it('should hide workspace skill selection when skill is disabled', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.providerContext.enableSkill = false
|
||||
mocks.skillEnabled = false
|
||||
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
|
||||
@ -31,7 +31,6 @@ import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SearchInput } from '@/app/components/base/search-input'
|
||||
import { SkeletonRectangle } from '@/app/components/base/skeleton'
|
||||
import { useProviderContextSelector } from '@/context/provider-context'
|
||||
import {
|
||||
agentComposerSkillsAtom,
|
||||
removeAgentSkillAtom,
|
||||
@ -467,7 +466,11 @@ export function AgentSkills() {
|
||||
const [isUploadOpen, setIsUploadOpen] = useState(false)
|
||||
const promptAddCallbackRef = useRef<AgentOrchestrateAddActionOptions['onAdded']>(undefined)
|
||||
const apiContext = useAgentConfigApiContext()
|
||||
const enableSkill = useProviderContextSelector((state) => state.enableSkill)
|
||||
const { data: enableSkill } = useQuery(
|
||||
consoleQuery.features.get.queryOptions({
|
||||
select: (features) => features.enable_skill,
|
||||
}),
|
||||
)
|
||||
const skills = useAtomValue(agentComposerSkillsAtom)
|
||||
const upsertAgentSkill = useSetAtom(upsertAgentSkillAtom)
|
||||
const removeAgentSkill = useSetAtom(removeAgentSkillAtom)
|
||||
@ -487,7 +490,7 @@ export function AgentSkills() {
|
||||
})
|
||||
const agentSkillBindingsQuery = useQuery({
|
||||
...agentSkillBindingsQueryOptions,
|
||||
enabled: enableSkill,
|
||||
enabled: enableSkill === true,
|
||||
})
|
||||
const hasLoadedAgentSkillBindings = agentSkillBindingsQuery.data !== undefined
|
||||
const { isPending: isReplacingAgentSkillBindings, mutate: replaceAgentSkillBindings } =
|
||||
|
||||
Loading…
Reference in New Issue
Block a user