From 44a3ea7c0c0da5489d2975340931ebecdd777cb3 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:10:57 +0800 Subject: [PATCH] refactor(web): localize model load balancing modal state (#39789) --- .../settings/__tests__/index.spec.tsx | 1 - .../__tests__/model-list.spec.tsx | 105 ++++++++++++---- .../provider-added-card/model-list.tsx | 117 ++++++++++-------- .../__tests__/index.spec.tsx | 1 - web/context/modal-context-provider.tsx | 15 --- web/context/modal-context.ts | 3 - 6 files changed, 147 insertions(+), 95 deletions(-) diff --git a/web/app/components/app/overview/settings/__tests__/index.spec.tsx b/web/app/components/app/overview/settings/__tests__/index.spec.tsx index 0096191f425..eb849273598 100644 --- a/web/app/components/app/overview/settings/__tests__/index.spec.tsx +++ b/web/app/components/app/overview/settings/__tests__/index.spec.tsx @@ -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(), diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx index c838ce63cfa..31b71e4845c 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/model-list.spec.tsx @@ -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) { + 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 ? ( +
+ {provider.provider} + + +
+ ) : null, })) vi.mock('../model-list-item', () => ({ @@ -34,7 +62,11 @@ vi.mock('../model-list-item', () => ({ model: ModelItem onModifyLoadBalancing: (model: ModelItem) => void }) => ( - ), @@ -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( { />, ) - 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( { />, ) - 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', () => { diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx index 298210b4faa..3823d366781 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-list.tsx @@ -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 = ({ 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(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 ( -
-
-
- - - {t(($) => $['modelProvider.modelsNum'], { ns: 'common', num: models.length })} - + <> +
+
+
+ + + {t(($) => $['modelProvider.modelsNum'], { ns: 'common', num: models.length })} + + + - - - {isConfigurable && canConfigureModels && ( -
- - -
- )} + {isConfigurable && canConfigureModels && ( +
+ + +
+ )} +
+ {models.map((model) => ( + + ))}
- {models.map((model) => ( - - ))}
-
+ {modelLoadBalancingModalProps && ( + setModelLoadBalancingModalProps(null)} + onSave={onChange} + /> + )} + ) } diff --git a/web/app/components/tools/edit-custom-collection-modal/__tests__/index.spec.tsx b/web/app/components/tools/edit-custom-collection-modal/__tests__/index.spec.tsx index 39505b3b2c0..3ef9f9a29a6 100644 --- a/web/app/components/tools/edit-custom-collection-modal/__tests__/index.spec.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/__tests__/index.spec.tsx @@ -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(), diff --git a/web/context/modal-context-provider.tsx b/web/context/modal-context-provider.tsx index fcd67b49892..c5b3b5fb483 100644 --- a/web/context/modal-context-provider.tsx +++ b/web/context/modal-context-provider.tsx @@ -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 | null>(null) const [showExternalKnowledgeAPIModal, setShowExternalKnowledgeAPIModal] = useState | null>(null) - const [showModelLoadBalancingModal, setShowModelLoadBalancingModal] = - useState(null) const [showOpeningModal, setShowOpeningModal] = useState 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) && ( - - )} {showOpeningModal && ( | null> > - setShowModelLoadBalancingModal: Dispatch> setShowOpeningModal: Dispatch< SetStateAction({ setShowAnnotationFullModal: noop, setShowModelModal: noop, setShowExternalKnowledgeAPIModal: noop, - setShowModelLoadBalancingModal: noop, setShowOpeningModal: noop, setShowUpdatePluginModal: noop, setShowTriggerEventsLimitModal: noop,