dify/web/context/modal-context-provider.tsx
Jingyi cb64446fa3
feat(web): add step-by-step tour shell (#38785)
Co-authored-by: hjlarry <hjlarry@163.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
2026-07-20 09:23:25 +00:00

439 lines
16 KiB
TypeScript

'use client'
import type { ReactNode, SetStateAction } 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 { SettingsTab } from '@/app/components/header/account-setting/constants'
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 { ExpireNoticeModalPayloadProps } from '@/app/education-apply/expire-notice-modal'
import type { ExternalDataTool } from '@/models/common'
import type { ModerationConfig, PromptVariable } from '@/models/debug'
import { useAtomValue } from 'jotai'
import { useCallback, useEffect, useRef, useState } from 'react'
import {
DEFAULT_ACCOUNT_SETTING_TAB,
isIntegrationSettingTab,
isUserSettingTab,
isValidSettingsTab,
isWorkspaceSettingTab,
} from '@/app/components/header/account-setting/constants'
import { useSetEducationVerifying } from '@/app/education-apply/storage'
import { useProviderContext } from '@/context/provider-context'
import { currentWorkspaceIdAtom } from '@/context/workspace-state'
import { useAccountSettingModal, usePricingModal } from '@/hooks/use-query-params'
import dynamic from '@/next/dynamic'
import { useTriggerEventsLimitModal } from './hooks/use-trigger-events-limit-modal'
import { ModalContext } from './modal-context'
const AccountSetting = dynamic(() => import('@/app/components/header/account-setting'), {
ssr: false,
})
const IntegrationsSettingModal = dynamic(
() => import('@/app/components/tools/integrations-setting-modal'),
{
ssr: false,
},
)
const ModerationSettingModal = dynamic(
() =>
import('@/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal'),
{
ssr: false,
},
)
const ExternalDataToolModal = dynamic(
() => import('@/app/components/app/configuration/tools/external-data-tool-modal'),
{
ssr: false,
},
)
const Pricing = dynamic(() => import('@/app/components/billing/pricing'), {
ssr: false,
})
const AnnotationFullModal = dynamic(
() => import('@/app/components/billing/annotation-full/modal'),
{
ssr: false,
},
)
const ModelModal = dynamic(
() => import('@/app/components/header/account-setting/model-provider-page/model-modal'),
{
ssr: false,
},
)
const ExternalAPIModal = dynamic(
() => import('@/app/components/datasets/external-api/external-api-modal'),
{
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'),
{
ssr: false,
},
)
const UpdatePlugin = dynamic(() => import('@/app/components/plugins/update-plugin'), {
ssr: false,
})
const ExpireNoticeModal = dynamic(() => import('@/app/education-apply/expire-notice-modal'), {
ssr: false,
})
const TriggerEventsLimitModal = dynamic(
() => import('@/app/components/billing/trigger-events-limit-modal'),
{
ssr: false,
},
)
type ModalContextProviderProps = {
children: ReactNode
}
export const ModalContextProvider = ({ children }: ModalContextProviderProps) => {
// Use nuqs hooks for URL-based modal state management
const [showPricingModal, setPricingModalOpen] = usePricingModal()
const [urlAccountModalState, setUrlAccountModalState] = useAccountSettingModal()
const accountSettingCallbacksRef = useRef<Omit<ModalState<SettingsTab>, 'payload'> | null>(null)
const settingsTab = urlAccountModalState.isOpen
? isValidSettingsTab(urlAccountModalState.payload)
? urlAccountModalState.payload
: DEFAULT_ACCOUNT_SETTING_TAB
: null
const accountSettingModalTab =
isWorkspaceSettingTab(settingsTab) || isUserSettingTab(settingsTab) ? settingsTab : null
const integrationSettingModalSection = isIntegrationSettingTab(settingsTab) ? settingsTab : null
const [showModerationSettingModal, setShowModerationSettingModal] =
useState<ModalState<ModerationConfig> | null>(null)
const [showExternalDataToolModal, setShowExternalDataToolModal] =
useState<ModalState<ExternalDataTool> | null>(null)
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[]
workflowVariables?: InputVar[]
onAutoAddPromptVariable?: (variable: PromptVariable[]) => void
}
> | null>(null)
const [showUpdatePluginModal, setShowUpdatePluginModal] =
useState<ModalState<UpdatePluginPayload> | null>(null)
const [showEducationExpireNoticeModal, setShowEducationExpireNoticeModal] =
useState<ModalState<ExpireNoticeModalPayloadProps> | null>(null)
const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom)
const setEducationVerifying = useSetEducationVerifying()
const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false)
const handleCancelAccountSettingModal = () => {
setEducationVerifying((educationVerifying) =>
educationVerifying === 'yes' ? null : educationVerifying,
)
accountSettingCallbacksRef.current?.onCancelCallback?.()
accountSettingCallbacksRef.current = null
setUrlAccountModalState(null)
}
const handleAccountSettingTabChange = useCallback(
(tab: SettingsTab) => {
setUrlAccountModalState({ payload: tab })
},
[setUrlAccountModalState],
)
const setShowAccountSettingModal = useCallback(
(next: SetStateAction<ModalState<SettingsTab> | null>) => {
const currentState = settingsTab
? { payload: settingsTab, ...accountSettingCallbacksRef.current }
: null
const resolvedState = typeof next === 'function' ? next(currentState) : next
if (!resolvedState) {
accountSettingCallbacksRef.current = null
setUrlAccountModalState(null)
return
}
const { payload, ...callbacks } = resolvedState
accountSettingCallbacksRef.current = callbacks
setUrlAccountModalState({ payload })
},
[settingsTab, setUrlAccountModalState],
)
useEffect(() => {
if (!urlAccountModalState.isOpen) accountSettingCallbacksRef.current = null
}, [urlAccountModalState.isOpen])
const { plan, isFetchedPlan } = useProviderContext()
const {
showTriggerEventsLimitModal,
setShowTriggerEventsLimitModal,
persistTriggerEventsLimitModalDismiss,
} = useTriggerEventsLimitModal({
plan,
isFetchedPlan,
currentWorkspaceId,
})
const handleCancelModerationSettingModal = () => {
setShowModerationSettingModal(null)
if (showModerationSettingModal?.onCancelCallback) showModerationSettingModal.onCancelCallback()
}
const handleCancelExternalDataToolModal = () => {
setShowExternalDataToolModal(null)
if (showExternalDataToolModal?.onCancelCallback) showExternalDataToolModal.onCancelCallback()
}
const handleCancelModelModal = useCallback(() => {
setShowModelModal(null)
if (showModelModal?.onCancelCallback) showModelModal.onCancelCallback()
}, [showModelModal])
const handleSaveModelModal = useCallback(
(formValues?: Record<string, unknown>) => {
if (showModelModal?.onSaveCallback)
showModelModal.onSaveCallback(showModelModal.payload, formValues)
setShowModelModal(null)
},
[showModelModal],
)
const handleRemoveModelModal = useCallback(
(formValues?: Record<string, unknown>) => {
if (showModelModal?.onRemoveCallback)
showModelModal.onRemoveCallback(showModelModal.payload, formValues)
setShowModelModal(null)
},
[showModelModal],
)
const handleCancelExternalApiModal = useCallback(() => {
setShowExternalKnowledgeAPIModal(null)
if (showExternalKnowledgeAPIModal?.onCancelCallback)
showExternalKnowledgeAPIModal.onCancelCallback()
}, [showExternalKnowledgeAPIModal])
const handleSaveExternalApiModal = useCallback(
async (updatedFormValue: CreateExternalAPIReq) => {
if (showExternalKnowledgeAPIModal?.onSaveCallback)
showExternalKnowledgeAPIModal.onSaveCallback(updatedFormValue)
setShowExternalKnowledgeAPIModal(null)
},
[showExternalKnowledgeAPIModal],
)
const handleEditExternalApiModal = useCallback(
async (updatedFormValue: CreateExternalAPIReq) => {
if (showExternalKnowledgeAPIModal?.onEditCallback)
showExternalKnowledgeAPIModal.onEditCallback(updatedFormValue)
setShowExternalKnowledgeAPIModal(null)
},
[showExternalKnowledgeAPIModal],
)
const handleCancelOpeningModal = useCallback(() => {
setShowOpeningModal(null)
if (showOpeningModal?.onCancelCallback) showOpeningModal.onCancelCallback()
}, [showOpeningModal])
const handleSaveModeration = (newModerationConfig: ModerationConfig) => {
if (showModerationSettingModal?.onSaveCallback)
showModerationSettingModal.onSaveCallback(newModerationConfig)
setShowModerationSettingModal(null)
}
const handleSaveExternalDataTool = (newExternalDataTool: ExternalDataTool) => {
if (showExternalDataToolModal?.onSaveCallback)
showExternalDataToolModal.onSaveCallback(newExternalDataTool)
setShowExternalDataToolModal(null)
}
const handleValidateBeforeSaveExternalDataTool = (newExternalDataTool: ExternalDataTool) => {
if (showExternalDataToolModal?.onValidateBeforeSaveCallback)
return showExternalDataToolModal?.onValidateBeforeSaveCallback(newExternalDataTool)
return true
}
const handleSaveOpeningModal = (newOpening: OpeningStatement) => {
if (showOpeningModal?.onSaveCallback) showOpeningModal.onSaveCallback(newOpening)
setShowOpeningModal(null)
}
const handleShowPricingModal = useCallback(() => {
setPricingModalOpen(true)
}, [setPricingModalOpen])
const handleCancelPricingModal = useCallback(() => {
setPricingModalOpen(false)
}, [setPricingModalOpen])
const hasBlockingModalOpen = Boolean(
accountSettingModalTab ||
integrationSettingModalSection ||
showModerationSettingModal ||
showExternalDataToolModal ||
showPricingModal ||
showAnnotationFullModal ||
showModelModal ||
showExternalKnowledgeAPIModal ||
showModelLoadBalancingModal ||
showOpeningModal ||
showUpdatePluginModal ||
showEducationExpireNoticeModal ||
showTriggerEventsLimitModal,
)
return (
<ModalContext.Provider
value={{
hasBlockingModalOpen,
setShowAccountSettingModal,
setShowModerationSettingModal,
setShowExternalDataToolModal,
setShowPricingModal: handleShowPricingModal,
setShowAnnotationFullModal: () => setShowAnnotationFullModal(true),
setShowModelModal,
setShowExternalKnowledgeAPIModal,
setShowModelLoadBalancingModal,
setShowOpeningModal,
setShowUpdatePluginModal,
setShowEducationExpireNoticeModal,
setShowTriggerEventsLimitModal,
}}
>
<>
{children}
{accountSettingModalTab && (
<AccountSetting
activeTab={accountSettingModalTab}
onCancelAction={handleCancelAccountSettingModal}
onTabChangeAction={handleAccountSettingTabChange}
/>
)}
{integrationSettingModalSection && (
<IntegrationsSettingModal
section={integrationSettingModalSection}
source={accountSettingCallbacksRef.current?.source}
onCancel={handleCancelAccountSettingModal}
onSectionChange={(section) => setUrlAccountModalState({ payload: section })}
/>
)}
{!!showModerationSettingModal && (
<ModerationSettingModal
data={showModerationSettingModal.payload}
onCancel={handleCancelModerationSettingModal}
onSave={handleSaveModeration}
/>
)}
{!!showExternalDataToolModal && (
<ExternalDataToolModal
data={showExternalDataToolModal.payload}
onCancel={handleCancelExternalDataToolModal}
onSave={handleSaveExternalDataTool}
onValidateBeforeSave={handleValidateBeforeSaveExternalDataTool}
/>
)}
{!!showPricingModal && <Pricing onCancel={handleCancelPricingModal} />}
{showAnnotationFullModal && (
<AnnotationFullModal
show={showAnnotationFullModal}
onHide={() => setShowAnnotationFullModal(false)}
/>
)}
{!!showModelModal && (
<ModelModal
provider={showModelModal.payload.currentProvider}
configurateMethod={showModelModal.payload.currentConfigurationMethod}
currentCustomConfigurationModelFixedFields={
showModelModal.payload.currentCustomConfigurationModelFixedFields
}
isModelCredential={showModelModal.payload.isModelCredential}
credential={showModelModal.payload.credential}
model={showModelModal.payload.model}
mode={showModelModal.payload.mode}
onCancel={handleCancelModelModal}
onSave={handleSaveModelModal}
onRemove={handleRemoveModelModal}
/>
)}
{!!showExternalKnowledgeAPIModal && (
<ExternalAPIModal
data={showExternalKnowledgeAPIModal.payload}
datasetBindings={showExternalKnowledgeAPIModal.datasetBindings ?? []}
onSave={handleSaveExternalApiModal}
onCancel={handleCancelExternalApiModal}
onEdit={handleEditExternalApiModal}
isEditMode={showExternalKnowledgeAPIModal.isEditMode ?? false}
/>
)}
{Boolean(showModelLoadBalancingModal) && (
<ModelLoadBalancingModal {...showModelLoadBalancingModal!} />
)}
{showOpeningModal && (
<OpeningSettingModal
data={showOpeningModal.payload}
onSave={handleSaveOpeningModal}
onCancel={handleCancelOpeningModal}
promptVariables={showOpeningModal.payload.promptVariables}
workflowVariables={showOpeningModal.payload.workflowVariables}
onAutoAddPromptVariable={showOpeningModal.payload.onAutoAddPromptVariable}
/>
)}
{!!showUpdatePluginModal && (
<UpdatePlugin
{...showUpdatePluginModal.payload}
onCancel={() => {
setShowUpdatePluginModal(null)
showUpdatePluginModal.onCancelCallback?.()
}}
onSave={() => {
setShowUpdatePluginModal(null)
showUpdatePluginModal.onSaveCallback?.()
}}
/>
)}
{!!showEducationExpireNoticeModal && (
<ExpireNoticeModal
{...showEducationExpireNoticeModal.payload}
onClose={() => setShowEducationExpireNoticeModal(null)}
/>
)}
{!!showTriggerEventsLimitModal && (
<TriggerEventsLimitModal
show
usage={showTriggerEventsLimitModal.payload.usage}
total={showTriggerEventsLimitModal.payload.total}
resetInDays={showTriggerEventsLimitModal.payload.resetInDays}
onClose={() => {
persistTriggerEventsLimitModalDismiss()
setShowTriggerEventsLimitModal(null)
}}
onUpgrade={() => {
persistTriggerEventsLimitModalDismiss()
setShowTriggerEventsLimitModal(null)
handleShowPricingModal()
}}
/>
)}
</>
</ModalContext.Provider>
)
}