mirror of
https://github.com/langgenius/dify.git
synced 2026-07-31 17:29:37 +08:00
refactor(web): localize model load balancing modal state (#39789)
This commit is contained in:
parent
1fd9ab14f9
commit
44a3ea7c0c
@ -69,7 +69,6 @@ const buildModalContext = (): ModalContextState => ({
|
||||
setShowAnnotationFullModal: vi.fn(),
|
||||
setShowModelModal: vi.fn(),
|
||||
setShowExternalKnowledgeAPIModal: vi.fn(),
|
||||
setShowModelLoadBalancingModal: vi.fn(),
|
||||
setShowOpeningModal: vi.fn(),
|
||||
setShowUpdatePluginModal: vi.fn(),
|
||||
setShowTriggerEventsLimitModal: vi.fn(),
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import type { ModelItem, ModelProvider } from '../../declarations'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import type { ModelLoadBalancingModalProps } from '../model-load-balancing-modal'
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import * as React from 'react'
|
||||
import { render } from '@/test/console/render'
|
||||
import { ConfigurationMethodEnum } from '../../declarations'
|
||||
import ModelList from '../model-list'
|
||||
|
||||
const mockSetShowModelLoadBalancingModal = vi.fn()
|
||||
let mockWorkspacePermissionKeys: string[] = [
|
||||
'plugin.model_config',
|
||||
'credential.manage',
|
||||
@ -18,12 +20,38 @@ vi.mock('@/context/permission-state', async () => {
|
||||
}))
|
||||
})
|
||||
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContextSelector: (
|
||||
selector: (state: {
|
||||
setShowModelLoadBalancingModal: typeof mockSetShowModelLoadBalancingModal
|
||||
}) => unknown,
|
||||
) => selector({ setShowModelLoadBalancingModal: mockSetShowModelLoadBalancingModal }),
|
||||
vi.mock('@/next/dynamic', () => ({
|
||||
default: (loader: () => Promise<{ default: React.ComponentType }>) => {
|
||||
const LazyComponent = React.lazy(loader)
|
||||
return function DynamicComponent(props: Record<string, unknown>) {
|
||||
return React.createElement(
|
||||
React.Suspense,
|
||||
{ fallback: null },
|
||||
React.createElement(LazyComponent, props),
|
||||
)
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../model-load-balancing-modal', () => ({
|
||||
default: ({ model, onClose, onSave, open, provider }: ModelLoadBalancingModalProps) =>
|
||||
open ? (
|
||||
<div role="dialog" aria-label={`Load balancing for ${model.model}`}>
|
||||
<span>{provider.provider}</span>
|
||||
<button type="button" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onSave?.(provider.provider)
|
||||
onClose?.()
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
) : null,
|
||||
}))
|
||||
|
||||
vi.mock('../model-list-item', () => ({
|
||||
@ -34,7 +62,11 @@ vi.mock('../model-list-item', () => ({
|
||||
model: ModelItem
|
||||
onModifyLoadBalancing: (model: ModelItem) => void
|
||||
}) => (
|
||||
<button type="button" onClick={() => onModifyLoadBalancing(model)}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`Modify load balancing for ${model.model}`}
|
||||
onClick={() => onModifyLoadBalancing(model)}
|
||||
>
|
||||
{model.model}
|
||||
</button>
|
||||
),
|
||||
@ -74,8 +106,12 @@ describe('ModelList', () => {
|
||||
/>,
|
||||
)
|
||||
expect(screen.getAllByText(/modelProvider\.modelsNum/).length).toBeGreaterThan(0)
|
||||
expect(screen.getByRole('button', { name: 'gpt-4' }))!.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'gpt-3.5' }))!.toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Modify load balancing for gpt-4' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Modify load balancing for gpt-3.5' }),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should trigger collapse when collapsed label is clicked', () => {
|
||||
@ -93,7 +129,8 @@ describe('ModelList', () => {
|
||||
expect(mockOnCollapse).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should open load balancing modal for selected model', () => {
|
||||
it('should open the selected model and reset the payload after close', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<ModelList
|
||||
provider={mockProvider}
|
||||
@ -103,8 +140,28 @@ describe('ModelList', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'gpt-4' }))
|
||||
expect(mockSetShowModelLoadBalancingModal).toHaveBeenCalled()
|
||||
await user.click(screen.getByRole('button', { name: 'Modify load balancing for gpt-4' }))
|
||||
|
||||
const firstDialog = await screen.findByRole('dialog', {
|
||||
name: 'Load balancing for gpt-4',
|
||||
})
|
||||
expect(within(firstDialog).getByText('test-provider')).toBeInTheDocument()
|
||||
|
||||
await user.click(within(firstDialog).getByRole('button', { name: 'Close' }))
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: 'Load balancing for gpt-4' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'Modify load balancing for gpt-3.5' }))
|
||||
|
||||
expect(
|
||||
await screen.findByRole('dialog', { name: 'Load balancing for gpt-3.5' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: 'Load balancing for gpt-4' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide custom model actions without plugin.model_config', () => {
|
||||
@ -173,7 +230,8 @@ describe('ModelList', () => {
|
||||
expect(screen.queryByTestId('add-custom-model')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onSave (onChange) and onClose from the load balancing modal callbacks', () => {
|
||||
it('should refresh the provider and close after saving load balancing changes', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<ModelList
|
||||
provider={mockProvider}
|
||||
@ -183,16 +241,19 @@ describe('ModelList', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'gpt-4' }))
|
||||
expect(mockSetShowModelLoadBalancingModal).toHaveBeenCalled()
|
||||
await user.click(screen.getByRole('button', { name: 'Modify load balancing for gpt-4' }))
|
||||
const dialog = await screen.findByRole('dialog', {
|
||||
name: 'Load balancing for gpt-4',
|
||||
})
|
||||
|
||||
const callArg = mockSetShowModelLoadBalancingModal.mock.calls[0]![0]
|
||||
await user.click(within(dialog).getByRole('button', { name: 'Save' }))
|
||||
|
||||
callArg.onSave('test-provider')
|
||||
expect(mockOnChange).toHaveBeenCalledWith('test-provider')
|
||||
|
||||
callArg.onClose()
|
||||
expect(mockSetShowModelLoadBalancingModal).toHaveBeenCalledWith(null)
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: 'Load balancing for gpt-4' }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should hide custom model actions when provider uses fetchFromRemote only', () => {
|
||||
|
||||
@ -1,19 +1,24 @@
|
||||
import type { FC } from 'react'
|
||||
import type { Credential, ModelItem, ModelProvider } from '../declarations'
|
||||
import type { ModelLoadBalancingModalProps } from './model-load-balancing-modal'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
AddCustomModel,
|
||||
ManageCustomModelCredentials,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { workspacePermissionKeysAtom } from '@/context/permission-state'
|
||||
import dynamic from '@/next/dynamic'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import { ConfigurationMethodEnum } from '../declarations'
|
||||
// import Tab from './tab'
|
||||
import ModelListItem from './model-list-item'
|
||||
|
||||
const ModelLoadBalancingModal = dynamic(() => import('./model-load-balancing-modal'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
type ModelListProps = {
|
||||
provider: ModelProvider
|
||||
models: ModelItem[]
|
||||
@ -28,70 +33,76 @@ const ModelList: FC<ModelListProps> = ({ provider, models, onCollapse, onChange
|
||||
const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom)
|
||||
const canConfigureModels = hasPermission(workspacePermissionKeys, 'plugin.model_config')
|
||||
const isConfigurable = configurativeMethods.includes(ConfigurationMethodEnum.customizableModel)
|
||||
const setShowModelLoadBalancingModal = useModalContextSelector(
|
||||
(state) => state.setShowModelLoadBalancingModal,
|
||||
)
|
||||
const [modelLoadBalancingModalProps, setModelLoadBalancingModalProps] =
|
||||
useState<ModelLoadBalancingModalProps | null>(null)
|
||||
const onModifyLoadBalancing = useCallback(
|
||||
(model: ModelItem, credential?: Credential) => {
|
||||
setShowModelLoadBalancingModal({
|
||||
setModelLoadBalancingModalProps({
|
||||
provider,
|
||||
credential,
|
||||
configurateMethod: model.fetch_from,
|
||||
model: model!,
|
||||
open: !!model,
|
||||
onClose: () => setShowModelLoadBalancingModal(null),
|
||||
onSave: onChange,
|
||||
model,
|
||||
open: true,
|
||||
})
|
||||
},
|
||||
[onChange, provider, setShowModelLoadBalancingModal],
|
||||
[provider],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="rounded-b-xl px-2 pb-2">
|
||||
<div className="rounded-lg bg-components-panel-bg py-1">
|
||||
<div className="flex items-center pr-0.75 pl-1">
|
||||
<span className="group mr-2 flex shrink-0 items-center">
|
||||
<span className="inline-flex h-6 items-center pr-1.5 pl-1 system-xs-medium text-text-tertiary group-hover:hidden">
|
||||
{t(($) => $['modelProvider.modelsNum'], { ns: 'common', num: models.length })}
|
||||
<span className="mr-0.5 i-ri-arrow-right-s-line size-4 rotate-90" />
|
||||
<>
|
||||
<div className="rounded-b-xl px-2 pb-2">
|
||||
<div className="rounded-lg bg-components-panel-bg py-1">
|
||||
<div className="flex items-center pr-0.75 pl-1">
|
||||
<span className="group mr-2 flex shrink-0 items-center">
|
||||
<span className="inline-flex h-6 items-center pr-1.5 pl-1 system-xs-medium text-text-tertiary group-hover:hidden">
|
||||
{t(($) => $['modelProvider.modelsNum'], { ns: 'common', num: models.length })}
|
||||
<span className="mr-0.5 i-ri-arrow-right-s-line size-4 rotate-90" />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="hidden h-6 cursor-pointer items-center rounded-lg border-none bg-state-base-hover pr-1.5 pl-1 system-xs-medium text-text-tertiary group-hover:inline-flex"
|
||||
onClick={() => onCollapse()}
|
||||
>
|
||||
{t(($) => $['modelProvider.modelsNum'], { ns: 'common', num: models.length })}
|
||||
<span className="mr-0.5 i-ri-arrow-right-s-line size-4 rotate-90" />
|
||||
</button>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="hidden h-6 cursor-pointer items-center rounded-lg border-none bg-state-base-hover pr-1.5 pl-1 system-xs-medium text-text-tertiary group-hover:inline-flex"
|
||||
onClick={() => onCollapse()}
|
||||
>
|
||||
{t(($) => $['modelProvider.modelsNum'], { ns: 'common', num: models.length })}
|
||||
<span className="mr-0.5 i-ri-arrow-right-s-line size-4 rotate-90" />
|
||||
</button>
|
||||
</span>
|
||||
{isConfigurable && canConfigureModels && (
|
||||
<div className="flex grow justify-end">
|
||||
<ManageCustomModelCredentials
|
||||
provider={provider}
|
||||
currentCustomConfigurationModelFixedFields={undefined}
|
||||
/>
|
||||
<AddCustomModel
|
||||
provider={provider}
|
||||
configurationMethod={ConfigurationMethodEnum.customizableModel}
|
||||
currentCustomConfigurationModelFixedFields={undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isConfigurable && canConfigureModels && (
|
||||
<div className="flex grow justify-end">
|
||||
<ManageCustomModelCredentials
|
||||
provider={provider}
|
||||
currentCustomConfigurationModelFixedFields={undefined}
|
||||
/>
|
||||
<AddCustomModel
|
||||
provider={provider}
|
||||
configurationMethod={ConfigurationMethodEnum.customizableModel}
|
||||
currentCustomConfigurationModelFixedFields={undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{models.map((model) => (
|
||||
<ModelListItem
|
||||
key={`${model.model}-${model.model_type}-${model.fetch_from}`}
|
||||
{...{
|
||||
model,
|
||||
provider,
|
||||
isConfigurable,
|
||||
onChange,
|
||||
onModifyLoadBalancing,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{models.map((model) => (
|
||||
<ModelListItem
|
||||
key={`${model.model}-${model.model_type}-${model.fetch_from}`}
|
||||
{...{
|
||||
model,
|
||||
provider,
|
||||
isConfigurable,
|
||||
onChange,
|
||||
onModifyLoadBalancing,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{modelLoadBalancingModalProps && (
|
||||
<ModelLoadBalancingModal
|
||||
{...modelLoadBalancingModalProps}
|
||||
onClose={() => setModelLoadBalancingModalProps(null)}
|
||||
onSave={onChange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -31,7 +31,6 @@ vi.mock('@/context/modal-context', () => ({
|
||||
setShowAnnotationFullModal: vi.fn(),
|
||||
setShowModelModal: vi.fn(),
|
||||
setShowExternalKnowledgeAPIModal: vi.fn(),
|
||||
setShowModelLoadBalancingModal: vi.fn(),
|
||||
setShowOpeningModal: vi.fn(),
|
||||
setShowUpdatePluginModal: vi.fn(),
|
||||
setShowTriggerEventsLimitModal: vi.fn(),
|
||||
|
||||
@ -4,7 +4,6 @@ import type { ReactNode } from 'react'
|
||||
import type { ModalState, ModelModalType } from './modal-context'
|
||||
import type { OpeningStatement } from '@/app/components/base/features/types'
|
||||
import type { CreateExternalAPIReq } from '@/app/components/datasets/external-api/declarations'
|
||||
import type { ModelLoadBalancingModalProps } from '@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal'
|
||||
import type { UpdatePluginPayload } from '@/app/components/plugins/types'
|
||||
import type { InputVar } from '@/app/components/workflow/types'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
@ -52,13 +51,6 @@ const ExternalAPIModal = dynamic(
|
||||
ssr: false,
|
||||
},
|
||||
)
|
||||
const ModelLoadBalancingModal = dynamic(
|
||||
() =>
|
||||
import('@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal'),
|
||||
{
|
||||
ssr: false,
|
||||
},
|
||||
)
|
||||
const OpeningSettingModal = dynamic(
|
||||
() => import('@/app/components/base/features/new-feature-panel/conversation-opener/modal'),
|
||||
{
|
||||
@ -88,8 +80,6 @@ export const ModalContextProvider = ({ children }: ModalContextProviderProps) =>
|
||||
const [showModelModal, setShowModelModal] = useState<ModalState<ModelModalType> | null>(null)
|
||||
const [showExternalKnowledgeAPIModal, setShowExternalKnowledgeAPIModal] =
|
||||
useState<ModalState<CreateExternalAPIReq> | null>(null)
|
||||
const [showModelLoadBalancingModal, setShowModelLoadBalancingModal] =
|
||||
useState<ModelLoadBalancingModalProps | null>(null)
|
||||
const [showOpeningModal, setShowOpeningModal] = useState<ModalState<
|
||||
OpeningStatement & {
|
||||
promptVariables?: PromptVariable[]
|
||||
@ -212,7 +202,6 @@ export const ModalContextProvider = ({ children }: ModalContextProviderProps) =>
|
||||
showAnnotationFullModal ||
|
||||
showModelModal ||
|
||||
showExternalKnowledgeAPIModal ||
|
||||
showModelLoadBalancingModal ||
|
||||
showOpeningModal ||
|
||||
showUpdatePluginModal ||
|
||||
showTriggerEventsLimitModal,
|
||||
@ -228,7 +217,6 @@ export const ModalContextProvider = ({ children }: ModalContextProviderProps) =>
|
||||
setShowAnnotationFullModal: () => setShowAnnotationFullModal(true),
|
||||
setShowModelModal,
|
||||
setShowExternalKnowledgeAPIModal,
|
||||
setShowModelLoadBalancingModal,
|
||||
setShowOpeningModal,
|
||||
setShowUpdatePluginModal,
|
||||
setShowTriggerEventsLimitModal,
|
||||
@ -286,9 +274,6 @@ export const ModalContextProvider = ({ children }: ModalContextProviderProps) =>
|
||||
isEditMode={showExternalKnowledgeAPIModal.isEditMode ?? false}
|
||||
/>
|
||||
)}
|
||||
{Boolean(showModelLoadBalancingModal) && (
|
||||
<ModelLoadBalancingModal {...showModelLoadBalancingModal!} />
|
||||
)}
|
||||
{showOpeningModal && (
|
||||
<OpeningSettingModal
|
||||
data={showOpeningModal.payload}
|
||||
|
||||
@ -12,7 +12,6 @@ import type {
|
||||
ModelModalModeEnum,
|
||||
ModelProvider,
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { ModelLoadBalancingModalProps } from '@/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-modal'
|
||||
import type { UpdatePluginPayload } from '@/app/components/plugins/types'
|
||||
import type { InputVar } from '@/app/components/workflow/types'
|
||||
import type { ExternalDataTool } from '@/models/common'
|
||||
@ -51,7 +50,6 @@ export type ModalContextState = {
|
||||
setShowExternalKnowledgeAPIModal: Dispatch<
|
||||
SetStateAction<ModalState<CreateExternalAPIReq> | null>
|
||||
>
|
||||
setShowModelLoadBalancingModal: Dispatch<SetStateAction<ModelLoadBalancingModalProps | null>>
|
||||
setShowOpeningModal: Dispatch<
|
||||
SetStateAction<ModalState<
|
||||
OpeningStatement & {
|
||||
@ -75,7 +73,6 @@ export const ModalContext = createContext<ModalContextState>({
|
||||
setShowAnnotationFullModal: noop,
|
||||
setShowModelModal: noop,
|
||||
setShowExternalKnowledgeAPIModal: noop,
|
||||
setShowModelLoadBalancingModal: noop,
|
||||
setShowOpeningModal: noop,
|
||||
setShowUpdatePluginModal: noop,
|
||||
setShowTriggerEventsLimitModal: noop,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user