fix: wrong component wrapping cause excessive app rerender
This commit is contained in:
parent
697fdc235d
commit
eab19b30cf
@ -52,9 +52,8 @@ export default function RootLayout() {
|
||||
<body className="font-sans antialiased">
|
||||
<JotaiWrapper>
|
||||
<ThemeWrapper>
|
||||
<ClipboardListener>
|
||||
<Search />
|
||||
</ClipboardListener>
|
||||
<ClipboardListener />
|
||||
<Search />
|
||||
</ThemeWrapper>
|
||||
</JotaiWrapper>
|
||||
</body>
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import { ReactNode, useCallback, useEffect, useRef } from 'react'
|
||||
import { PropsWithChildren, useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
import { ScrollArea } from '@janhq/joi'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const ListContainer = ({ children }: Props) => {
|
||||
const ListContainer = ({ children }: PropsWithChildren) => {
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
const prevScrollTop = useRef(0)
|
||||
const isUserManuallyScrollingUp = useRef(false)
|
||||
|
||||
@ -2,7 +2,6 @@ import '@testing-library/jest-dom'
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import ModelConfigInput from './index'
|
||||
import { Tooltip } from '@janhq/joi'
|
||||
|
||||
// Mocking the Tooltip component to simplify testing
|
||||
jest.mock('@janhq/joi', () => ({
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Fragment, PropsWithChildren, useEffect } from 'react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
|
||||
import { AppUpdateInfo } from '@janhq/core'
|
||||
import { useSetAtom } from 'jotai'
|
||||
@ -8,7 +8,7 @@ import {
|
||||
updateVersionErrorAtom,
|
||||
} from '@/helpers/atoms/App.atom'
|
||||
|
||||
const AppUpdateListener = ({ children }: PropsWithChildren) => {
|
||||
const AppUpdateListener = () => {
|
||||
const setProgress = useSetAtom(appDownloadProgressAtom)
|
||||
const setUpdateVersionError = useSetAtom(updateVersionErrorAtom)
|
||||
|
||||
@ -39,7 +39,7 @@ const AppUpdateListener = ({ children }: PropsWithChildren) => {
|
||||
}
|
||||
}, [setProgress, setUpdateVersionError])
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
export default AppUpdateListener
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Fragment, PropsWithChildren } from 'react'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
import { selectedTextAtom } from './Jotai'
|
||||
|
||||
const ClipboardListener = ({ children }: PropsWithChildren) => {
|
||||
const ClipboardListener = () => {
|
||||
const setSelectedText = useSetAtom(selectedTextAtom)
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
@ -13,7 +13,7 @@ const ClipboardListener = ({ children }: PropsWithChildren) => {
|
||||
})
|
||||
}
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
export default ClipboardListener
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { Fragment, ReactNode, useEffect } from 'react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
|
||||
import { AppConfiguration, getUserHomePath } from '@janhq/core'
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
import useAssistants from '@/hooks/useAssistants'
|
||||
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
||||
import { useLoadTheme } from '@/hooks/useLoadTheme'
|
||||
import useModels from '@/hooks/useModels'
|
||||
import useThreads from '@/hooks/useThreads'
|
||||
|
||||
@ -20,27 +19,20 @@ import {
|
||||
} from '@/helpers/atoms/AppConfig.atom'
|
||||
import { janSettingScreenAtom } from '@/helpers/atoms/Setting.atom'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const DataLoader: React.FC<Props> = ({ children }) => {
|
||||
const DataLoader: React.FC = () => {
|
||||
const setJanDataFolderPath = useSetAtom(janDataFolderPathAtom)
|
||||
const setQuickAskEnabled = useSetAtom(quickAskEnabledAtom)
|
||||
const setJanDefaultDataFolder = useSetAtom(defaultJanDataFolderAtom)
|
||||
const setJanSettingScreen = useSetAtom(janSettingScreenAtom)
|
||||
const { loadDataModel, configurePullOptions } = useModels()
|
||||
const { getData: loadModels } = useModels()
|
||||
|
||||
useThreads()
|
||||
useAssistants()
|
||||
useGetSystemResources()
|
||||
useLoadTheme()
|
||||
|
||||
useEffect(() => {
|
||||
// Load data once
|
||||
loadDataModel()
|
||||
// Configure pull options once
|
||||
configurePullOptions()
|
||||
loadModels()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
@ -71,7 +63,7 @@ const DataLoader: React.FC<Props> = ({ children }) => {
|
||||
|
||||
console.debug('Load Data...')
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
export default DataLoader
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Fragment, ReactNode } from 'react'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
@ -13,11 +13,8 @@ import {
|
||||
importHuggingFaceModelStageAtom,
|
||||
importingHuggingFaceRepoDataAtom,
|
||||
} from '@/helpers/atoms/HuggingFace.atom'
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const DeepLinkListener: React.FC<Props> = ({ children }) => {
|
||||
const DeepLinkListener: React.FC = () => {
|
||||
const { getHfRepoData } = useGetHFRepoData()
|
||||
const setLoadingInfo = useSetAtom(loadingModalInfoAtom)
|
||||
const setImportingHuggingFaceRepoData = useSetAtom(
|
||||
@ -69,7 +66,7 @@ const DeepLinkListener: React.FC<Props> = ({ children }) => {
|
||||
handleDeepLinkAction(action)
|
||||
})
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
type DeepLinkAction = {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { PropsWithChildren, useCallback, useEffect } from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
@ -23,7 +23,7 @@ import { toaster } from '../Toast'
|
||||
|
||||
import AppUpdateListener from './AppUpdateListener'
|
||||
import ClipboardListener from './ClipboardListener'
|
||||
import EventHandler from './EventHandler'
|
||||
import ModelHandler from './ModelHandler'
|
||||
|
||||
import ModelImportListener from './ModelImportListener'
|
||||
import QuickAskListener from './QuickAskListener'
|
||||
@ -39,7 +39,7 @@ import {
|
||||
removeDownloadingModelAtom,
|
||||
} from '@/helpers/atoms/Model.atom'
|
||||
|
||||
const EventListenerWrapper = ({ children }: PropsWithChildren) => {
|
||||
const EventListener = () => {
|
||||
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
||||
const setInstallingExtension = useSetAtom(setInstallingExtensionAtom)
|
||||
const removeInstallingExtension = useSetAtom(removeInstallingExtensionAtom)
|
||||
@ -156,16 +156,14 @@ const EventListenerWrapper = ({ children }: PropsWithChildren) => {
|
||||
])
|
||||
|
||||
return (
|
||||
<AppUpdateListener>
|
||||
<ClipboardListener>
|
||||
<ModelImportListener>
|
||||
<QuickAskListener>
|
||||
<EventHandler>{children}</EventHandler>
|
||||
</QuickAskListener>
|
||||
</ModelImportListener>
|
||||
</ClipboardListener>
|
||||
</AppUpdateListener>
|
||||
<>
|
||||
<AppUpdateListener />
|
||||
<ClipboardListener />
|
||||
<ModelImportListener />
|
||||
<QuickAskListener />
|
||||
<ModelHandler />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EventListenerWrapper
|
||||
export default EventListener
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { ReactNode } from 'react'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
import { Provider, atom } from 'jotai'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const editPromptAtom = atom<string>('')
|
||||
export const currentPromptAtom = atom<string>('')
|
||||
export const fileUploadAtom = atom<FileInfo[]>([])
|
||||
@ -16,7 +12,7 @@ export const searchAtom = atom<string>('')
|
||||
|
||||
export const selectedTextAtom = atom('')
|
||||
|
||||
export default function JotaiWrapper({ children }: Props) {
|
||||
export default function JotaiWrapper({ children }: PropsWithChildren) {
|
||||
return <Provider>{children}</Provider>
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { Fragment, ReactNode, useEffect } from 'react'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
|
||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||
|
||||
@ -22,11 +22,7 @@ import {
|
||||
ThreadModalAction,
|
||||
} from '@/helpers/atoms/Thread.atom'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export default function KeyListener({ children }: Props) {
|
||||
export default function KeyListener() {
|
||||
const setShowLeftPanel = useSetAtom(showLeftPanelAtom)
|
||||
const setShowRightPanel = useSetAtom(showRightPanelAtom)
|
||||
const [mainViewState, setMainViewState] = useAtom(mainViewStateAtom)
|
||||
@ -94,5 +90,5 @@ export default function KeyListener({ children }: Props) {
|
||||
setShowRightPanel,
|
||||
])
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Fragment, ReactNode, useCallback, useEffect, useRef } from 'react'
|
||||
import { Fragment, useCallback, useEffect, useRef } from 'react'
|
||||
|
||||
import {
|
||||
ChatCompletionMessage,
|
||||
@ -43,7 +43,7 @@ import {
|
||||
const maxWordForThreadTitle = 10
|
||||
const defaultThreadTitle = 'New Thread'
|
||||
|
||||
export default function EventHandler({ children }: { children: ReactNode }) {
|
||||
export default function ModelHandler() {
|
||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||
const addNewMessage = useSetAtom(addNewMessageAtom)
|
||||
const updateMessage = useSetAtom(updateMessageAtom)
|
||||
@ -333,5 +333,5 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}, [onNewMessageResponse, onMessageResponseUpdate, onModelStopped])
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { Fragment, PropsWithChildren, useCallback, useEffect } from 'react'
|
||||
import { Fragment, useCallback, useEffect } from 'react'
|
||||
|
||||
import {
|
||||
ImportingModel,
|
||||
@ -17,7 +17,7 @@ import {
|
||||
updateImportingModelProgressAtom,
|
||||
} from '@/helpers/atoms/Model.atom'
|
||||
|
||||
const ModelImportListener = ({ children }: PropsWithChildren) => {
|
||||
const ModelImportListener = () => {
|
||||
const updateImportingModelProgress = useSetAtom(
|
||||
updateImportingModelProgressAtom
|
||||
)
|
||||
@ -103,7 +103,7 @@ const ModelImportListener = ({ children }: PropsWithChildren) => {
|
||||
onImportModelFailed,
|
||||
])
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
export default ModelImportListener
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Fragment, ReactNode } from 'react'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
@ -10,11 +10,7 @@ import useSendChatMessage from '@/hooks/useSendChatMessage'
|
||||
|
||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||
|
||||
type Props = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const QuickAskListener: React.FC<Props> = ({ children }) => {
|
||||
const QuickAskListener: React.FC = () => {
|
||||
const { sendChatMessage } = useSendChatMessage()
|
||||
const setMainState = useSetAtom(mainViewStateAtom)
|
||||
|
||||
@ -27,7 +23,7 @@ const QuickAskListener: React.FC<Props> = ({ children }) => {
|
||||
debounced(input)
|
||||
})
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
export default QuickAskListener
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { Fragment, PropsWithChildren, useEffect, useRef } from 'react'
|
||||
import { Fragment, useEffect, useRef } from 'react'
|
||||
|
||||
import { useMediaQuery } from '@janhq/joi'
|
||||
import { useAtom } from 'jotai'
|
||||
|
||||
import { showLeftPanelAtom, showRightPanelAtom } from '@/helpers/atoms/App.atom'
|
||||
|
||||
const Responsive = ({ children }: PropsWithChildren) => {
|
||||
const Responsive = () => {
|
||||
const matches = useMediaQuery('(max-width: 880px)')
|
||||
const [showLeftPanel, setShowLeftPanel] = useAtom(showLeftPanelAtom)
|
||||
const [showRightPanel, setShowRightPanel] = useAtom(showRightPanelAtom)
|
||||
@ -30,7 +30,7 @@ const Responsive = ({ children }: PropsWithChildren) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [matches, setShowLeftPanel, setShowRightPanel])
|
||||
|
||||
return <Fragment>{children}</Fragment>
|
||||
return <Fragment></Fragment>
|
||||
}
|
||||
|
||||
export default Responsive
|
||||
|
||||
20
web/containers/Providers/SettingsHandler.tsx
Normal file
20
web/containers/Providers/SettingsHandler.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useConfigurations } from '@/hooks/useConfigurations'
|
||||
import { useLoadTheme } from '@/hooks/useLoadTheme'
|
||||
|
||||
const SettingsHandler: React.FC = () => {
|
||||
useLoadTheme()
|
||||
|
||||
const { configurePullOptions } = useConfigurations()
|
||||
|
||||
useEffect(() => {
|
||||
configurePullOptions()
|
||||
}, [configurePullOptions])
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
export default SettingsHandler
|
||||
@ -5,7 +5,7 @@ import { PropsWithChildren, useCallback, useEffect, useState } from 'react'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
|
||||
import Loader from '@/containers/Loader'
|
||||
import EventListenerWrapper from '@/containers/Providers/EventListener'
|
||||
import EventListener from '@/containers/Providers/EventListener'
|
||||
import JotaiWrapper from '@/containers/Providers/Jotai'
|
||||
|
||||
import ThemeWrapper from '@/containers/Providers/Theme'
|
||||
@ -24,6 +24,8 @@ import DeepLinkListener from './DeepLinkListener'
|
||||
import KeyListener from './KeyListener'
|
||||
import Responsive from './Responsive'
|
||||
|
||||
import SettingsHandler from './SettingsHandler'
|
||||
|
||||
import { extensionManager } from '@/extension'
|
||||
|
||||
const Providers = ({ children }: PropsWithChildren) => {
|
||||
@ -76,16 +78,14 @@ const Providers = ({ children }: PropsWithChildren) => {
|
||||
{settingUp && <Loader description="Preparing Update..." />}
|
||||
{setupCore && activated && (
|
||||
<>
|
||||
<Responsive>
|
||||
<KeyListener>
|
||||
<EventListenerWrapper>
|
||||
<DataLoader>
|
||||
<DeepLinkListener>{children}</DeepLinkListener>
|
||||
</DataLoader>
|
||||
</EventListenerWrapper>
|
||||
<Toaster />
|
||||
</KeyListener>
|
||||
</Responsive>
|
||||
<Responsive />
|
||||
<KeyListener />
|
||||
<EventListener />
|
||||
<DataLoader />
|
||||
<SettingsHandler />
|
||||
<DeepLinkListener />
|
||||
<Toaster />
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
</JotaiWrapper>
|
||||
|
||||
41
web/hooks/useConfigurations.ts
Normal file
41
web/hooks/useConfigurations.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { useCallback, useEffect } from 'react'
|
||||
|
||||
import { ExtensionTypeEnum, ModelExtension } from '@janhq/core'
|
||||
import { useAtomValue } from 'jotai'
|
||||
|
||||
import { extensionManager } from '@/extension'
|
||||
import {
|
||||
ignoreSslAtom,
|
||||
proxyAtom,
|
||||
proxyEnabledAtom,
|
||||
} from '@/helpers/atoms/AppConfig.atom'
|
||||
|
||||
export const useConfigurations = () => {
|
||||
const proxyEnabled = useAtomValue(proxyEnabledAtom)
|
||||
const proxyUrl = useAtomValue(proxyAtom)
|
||||
const proxyIgnoreSSL = useAtomValue(ignoreSslAtom)
|
||||
|
||||
const configurePullOptions = useCallback(() => {
|
||||
extensionManager
|
||||
.get<ModelExtension>(ExtensionTypeEnum.Model)
|
||||
?.configurePullOptions(
|
||||
proxyEnabled
|
||||
? {
|
||||
proxy_url: proxyUrl,
|
||||
verify_peer_ssl: !proxyIgnoreSSL,
|
||||
}
|
||||
: {
|
||||
proxy_url: '',
|
||||
verify_peer_ssl: false,
|
||||
}
|
||||
)
|
||||
}, [proxyEnabled, proxyUrl, proxyIgnoreSSL])
|
||||
|
||||
useEffect(() => {
|
||||
configurePullOptions()
|
||||
}, [])
|
||||
|
||||
return {
|
||||
configurePullOptions,
|
||||
}
|
||||
}
|
||||
@ -9,18 +9,14 @@ import {
|
||||
ModelManager,
|
||||
} from '@janhq/core'
|
||||
|
||||
import { useSetAtom, useAtom, useAtomValue } from 'jotai'
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
|
||||
import { isLocalEngine } from '@/utils/modelEngine'
|
||||
|
||||
import { extensionManager } from '@/extension'
|
||||
import {
|
||||
ignoreSslAtom,
|
||||
proxyAtom,
|
||||
proxyEnabledAtom,
|
||||
} from '@/helpers/atoms/AppConfig.atom'
|
||||
|
||||
import {
|
||||
configuredModelsAtom,
|
||||
downloadedModelsAtom,
|
||||
@ -32,11 +28,8 @@ import {
|
||||
* and updates the atoms accordingly.
|
||||
*/
|
||||
const useModels = () => {
|
||||
const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom)
|
||||
const setDownloadedModels = useSetAtom(downloadedModelsAtom)
|
||||
const setExtensionModels = useSetAtom(configuredModelsAtom)
|
||||
const proxyEnabled = useAtomValue(proxyEnabledAtom)
|
||||
const proxyUrl = useAtomValue(proxyAtom)
|
||||
const proxyIgnoreSSL = useAtomValue(ignoreSslAtom)
|
||||
|
||||
const getData = useCallback(() => {
|
||||
const getDownloadedModels = async () => {
|
||||
@ -87,18 +80,17 @@ const useModels = () => {
|
||||
|
||||
const updateStates = useCallback(() => {
|
||||
const cachedModels = ModelManager.instance().models.values().toArray()
|
||||
const toUpdate = [
|
||||
setDownloadedModels((downloadedModels) => [
|
||||
...downloadedModels,
|
||||
...cachedModels.filter(
|
||||
(e) =>
|
||||
!isLocalEngine(e.engine) &&
|
||||
!downloadedModels.some((g: Model) => g.id === e.id)
|
||||
),
|
||||
]
|
||||
])
|
||||
|
||||
setDownloadedModels(toUpdate)
|
||||
setExtensionModels(cachedModels)
|
||||
}, [downloadedModels, setDownloadedModels, setExtensionModels])
|
||||
}, [setDownloadedModels, setExtensionModels])
|
||||
|
||||
const getModels = async (): Promise<Model[]> =>
|
||||
extensionManager
|
||||
@ -118,25 +110,8 @@ const useModels = () => {
|
||||
}
|
||||
}, [reloadData, updateStates])
|
||||
|
||||
const configurePullOptions = useCallback(() => {
|
||||
extensionManager
|
||||
.get<ModelExtension>(ExtensionTypeEnum.Model)
|
||||
?.configurePullOptions(
|
||||
proxyEnabled
|
||||
? {
|
||||
proxy_url: proxyUrl,
|
||||
verify_peer_ssl: !proxyIgnoreSSL,
|
||||
}
|
||||
: {
|
||||
proxy_url: '',
|
||||
verify_peer_ssl: false,
|
||||
}
|
||||
)
|
||||
}, [proxyEnabled, proxyUrl, proxyIgnoreSSL])
|
||||
|
||||
return {
|
||||
loadDataModel: getData,
|
||||
configurePullOptions,
|
||||
getData,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ import { useDebouncedCallback } from 'use-debounce'
|
||||
import { snackbar, toaster } from '@/containers/Toast'
|
||||
|
||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||
import useModels from '@/hooks/useModels'
|
||||
import { useConfigurations } from '@/hooks/useConfigurations'
|
||||
import { useSettings } from '@/hooks/useSettings'
|
||||
|
||||
import DataFolder from './DataFolder'
|
||||
@ -68,7 +68,7 @@ const Advanced = () => {
|
||||
const [dropdownOptions, setDropdownOptions] = useState<HTMLDivElement | null>(
|
||||
null
|
||||
)
|
||||
const { configurePullOptions } = useModels()
|
||||
const { configurePullOptions } = useConfigurations()
|
||||
|
||||
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user