'use client' import type { Dispatch, SetStateAction } from 'react' import { useCallback, useEffect, useState } from 'react' import { createContext, useContext, useContextSelector } from 'use-context-selector' import { useSearchParams } from 'next/navigation' import type { ConfigurationMethodEnum, Credential, CustomConfigurationModelFixedFields, CustomModel, ModelProvider, } from '@/app/components/header/account-setting/model-provider-page/declarations' import { EDUCATION_PRICING_SHOW_ACTION, EDUCATION_VERIFYING_LOCALSTORAGE_ITEM, } from '@/app/education-apply/constants' import type { AccountSettingTab } from '@/app/components/header/account-setting/constants' import { ACCOUNT_SETTING_MODAL_ACTION, DEFAULT_ACCOUNT_SETTING_TAB, isValidAccountSettingTab, } from '@/app/components/header/account-setting/constants' import type { ModerationConfig, PromptVariable } from '@/models/debug' import type { ApiBasedExtension, ExternalDataTool, } from '@/models/common' 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 { OpeningStatement } from '@/app/components/base/features/types' import type { InputVar } from '@/app/components/workflow/types' import type { UpdatePluginPayload } from '@/app/components/plugins/types' import { removeSpecificQueryParam } from '@/utils' import { noop } from 'lodash-es' import dynamic from 'next/dynamic' import type { ExpireNoticeModalPayloadProps } from '@/app/education-apply/expire-notice-modal' import type { ModelModalModeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useProviderContext } from '@/context/provider-context' import { useAppContext } from '@/context/app-context' import { type TriggerEventsLimitModalPayload, useTriggerEventsLimitModal, } from './hooks/use-trigger-events-limit-modal' const AccountSetting = dynamic(() => import('@/app/components/header/account-setting'), { ssr: false, }) const ApiBasedExtensionModal = dynamic(() => import('@/app/components/header/account-setting/api-based-extension-page/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, }) export type ModalState = { payload: T onCancelCallback?: () => void onSaveCallback?: (newPayload?: T, formValues?: Record) => void onRemoveCallback?: (newPayload?: T, formValues?: Record) => void onEditCallback?: (newPayload: T) => void onValidateBeforeSaveCallback?: (newPayload: T) => boolean isEditMode?: boolean datasetBindings?: { id: string; name: string }[] } export type ModelModalType = { currentProvider: ModelProvider currentConfigurationMethod: ConfigurationMethodEnum currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields isModelCredential?: boolean credential?: Credential model?: CustomModel mode?: ModelModalModeEnum } export type ModalContextState = { setShowAccountSettingModal: Dispatch | null>> setShowApiBasedExtensionModal: Dispatch | null>> setShowModerationSettingModal: Dispatch | null>> setShowExternalDataToolModal: Dispatch | null>> setShowPricingModal: () => void setShowAnnotationFullModal: () => void setShowModelModal: Dispatch | null>> setShowExternalKnowledgeAPIModal: Dispatch | null>> setShowModelLoadBalancingModal: Dispatch> setShowOpeningModal: Dispatch void }> | null>> setShowUpdatePluginModal: Dispatch | null>> setShowEducationExpireNoticeModal: Dispatch | null>> setShowTriggerEventsLimitModal: Dispatch | null>> } const PRICING_MODAL_QUERY_PARAM = 'pricing' const PRICING_MODAL_QUERY_VALUE = 'open' const ModalContext = createContext({ setShowAccountSettingModal: noop, setShowApiBasedExtensionModal: noop, setShowModerationSettingModal: noop, setShowExternalDataToolModal: noop, setShowPricingModal: noop, setShowAnnotationFullModal: noop, setShowModelModal: noop, setShowExternalKnowledgeAPIModal: noop, setShowModelLoadBalancingModal: noop, setShowOpeningModal: noop, setShowUpdatePluginModal: noop, setShowEducationExpireNoticeModal: noop, setShowTriggerEventsLimitModal: noop, }) export const useModalContext = () => useContext(ModalContext) // Adding a dangling comma to avoid the generic parsing issue in tsx, see: // https://github.com/microsoft/TypeScript/issues/15713 export const useModalContextSelector = (selector: (state: ModalContextState) => T): T => useContextSelector(ModalContext, selector) type ModalContextProviderProps = { children: React.ReactNode } export const ModalContextProvider = ({ children, }: ModalContextProviderProps) => { const searchParams = useSearchParams() const [showAccountSettingModal, setShowAccountSettingModal] = useState | null>(() => { if (searchParams.get('action') === ACCOUNT_SETTING_MODAL_ACTION) { const tabParam = searchParams.get('tab') const tab = isValidAccountSettingTab(tabParam) ? tabParam : DEFAULT_ACCOUNT_SETTING_TAB return { payload: tab } } return null }) const [showApiBasedExtensionModal, setShowApiBasedExtensionModal] = useState | null>(null) const [showModerationSettingModal, setShowModerationSettingModal] = useState | null>(null) const [showExternalDataToolModal, setShowExternalDataToolModal] = useState | null>(null) const [showModelModal, setShowModelModal] = useState | null>(null) const [showExternalKnowledgeAPIModal, setShowExternalKnowledgeAPIModal] = useState | null>(null) const [showModelLoadBalancingModal, setShowModelLoadBalancingModal] = useState(null) const [showOpeningModal, setShowOpeningModal] = useState void }> | null>(null) const [showUpdatePluginModal, setShowUpdatePluginModal] = useState | null>(null) const [showEducationExpireNoticeModal, setShowEducationExpireNoticeModal] = useState | null>(null) const { currentWorkspace } = useAppContext() const [showPricingModal, setShowPricingModal] = useState( searchParams.get(PRICING_MODAL_QUERY_PARAM) === PRICING_MODAL_QUERY_VALUE, ) const [showAnnotationFullModal, setShowAnnotationFullModal] = useState(false) const handleCancelAccountSettingModal = () => { const educationVerifying = localStorage.getItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM) if (educationVerifying === 'yes') localStorage.removeItem(EDUCATION_VERIFYING_LOCALSTORAGE_ITEM) removeSpecificQueryParam('action') removeSpecificQueryParam('tab') setShowAccountSettingModal(null) if (showAccountSettingModal?.onCancelCallback) showAccountSettingModal?.onCancelCallback() } const handleAccountSettingTabChange = useCallback((tab: AccountSettingTab) => { setShowAccountSettingModal((prev) => { if (!prev) return { payload: tab } if (prev.payload === tab) return prev return { ...prev, payload: tab } }) }, [setShowAccountSettingModal]) useEffect(() => { if (typeof window === 'undefined') return const url = new URL(window.location.href) if (!showAccountSettingModal?.payload) { if (url.searchParams.get('action') !== ACCOUNT_SETTING_MODAL_ACTION) return url.searchParams.delete('action') url.searchParams.delete('tab') window.history.replaceState(null, '', url.toString()) return } url.searchParams.set('action', ACCOUNT_SETTING_MODAL_ACTION) url.searchParams.set('tab', showAccountSettingModal.payload) window.history.replaceState(null, '', url.toString()) }, [showAccountSettingModal]) useEffect(() => { if (typeof window === 'undefined') return const url = new URL(window.location.href) if (showPricingModal) { url.searchParams.set(PRICING_MODAL_QUERY_PARAM, PRICING_MODAL_QUERY_VALUE) } else { url.searchParams.delete(PRICING_MODAL_QUERY_PARAM) if (url.searchParams.get('action') === EDUCATION_PRICING_SHOW_ACTION) url.searchParams.delete('action') } window.history.replaceState(null, '', url.toString()) }, [showPricingModal]) const { plan, isFetchedPlan } = useProviderContext() const { showTriggerEventsLimitModal, setShowTriggerEventsLimitModal, persistTriggerEventsLimitModalDismiss, } = useTriggerEventsLimitModal({ plan, isFetchedPlan, currentWorkspaceId: currentWorkspace?.id, }) 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) => { if (showModelModal?.onSaveCallback) showModelModal.onSaveCallback(showModelModal.payload, formValues) setShowModelModal(null) }, [showModelModal]) const handleRemoveModelModal = useCallback((formValues?: Record) => { 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 handleSaveApiBasedExtension = (newApiBasedExtension: ApiBasedExtension) => { if (showApiBasedExtensionModal?.onSaveCallback) showApiBasedExtensionModal.onSaveCallback(newApiBasedExtension) setShowApiBasedExtensionModal(null) } 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(() => { setShowPricingModal(true) }, []) const handleCancelPricingModal = useCallback(() => { setShowPricingModal(false) }, []) return ( setShowAnnotationFullModal(true), setShowModelModal, setShowExternalKnowledgeAPIModal, setShowModelLoadBalancingModal, setShowOpeningModal, setShowUpdatePluginModal, setShowEducationExpireNoticeModal, setShowTriggerEventsLimitModal, }}> <> {children} { !!showAccountSettingModal && ( ) } { !!showApiBasedExtensionModal && ( setShowApiBasedExtensionModal(null)} onSave={handleSaveApiBasedExtension} /> ) } { !!showModerationSettingModal && ( ) } { !!showExternalDataToolModal && ( ) } { !!showPricingModal && ( ) } { showAnnotationFullModal && ( setShowAnnotationFullModal(false)} /> ) } { !!showModelModal && ( ) } { !!showExternalKnowledgeAPIModal && ( ) } { Boolean(showModelLoadBalancingModal) && ( ) } {showOpeningModal && ( )} { !!showUpdatePluginModal && ( { setShowUpdatePluginModal(null) showUpdatePluginModal.onCancelCallback?.() }} onSave={() => { setShowUpdatePluginModal(null) showUpdatePluginModal.onSaveCallback?.({} as any) }} /> ) } { !!showEducationExpireNoticeModal && ( setShowEducationExpireNoticeModal(null)} /> )} { !!showTriggerEventsLimitModal && ( { persistTriggerEventsLimitModalDismiss() setShowTriggerEventsLimitModal(null) }} onUpgrade={() => { persistTriggerEventsLimitModalDismiss() setShowTriggerEventsLimitModal(null) handleShowPricingModal() }} /> )} ) } export default ModalContext