diff --git a/web/app/search/layout.tsx b/web/app/search/layout.tsx index 6c491c381..8af34dc00 100644 --- a/web/app/search/layout.tsx +++ b/web/app/search/layout.tsx @@ -52,9 +52,8 @@ export default function RootLayout() { - - - + + diff --git a/web/containers/ListContainer/index.tsx b/web/containers/ListContainer/index.tsx index 2b720fb43..c55287bc6 100644 --- a/web/containers/ListContainer/index.tsx +++ b/web/containers/ListContainer/index.tsx @@ -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(null) const prevScrollTop = useRef(0) const isUserManuallyScrollingUp = useRef(false) diff --git a/web/containers/ModelConfigInput/index.test.tsx b/web/containers/ModelConfigInput/index.test.tsx index b92bdfcb2..cf9cb9da3 100644 --- a/web/containers/ModelConfigInput/index.test.tsx +++ b/web/containers/ModelConfigInput/index.test.tsx @@ -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', () => ({ diff --git a/web/containers/Providers/AppUpdateListener.tsx b/web/containers/Providers/AppUpdateListener.tsx index 77b39bb06..4d05f6010 100644 --- a/web/containers/Providers/AppUpdateListener.tsx +++ b/web/containers/Providers/AppUpdateListener.tsx @@ -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 {children} + return } export default AppUpdateListener diff --git a/web/containers/Providers/ClipboardListener.tsx b/web/containers/Providers/ClipboardListener.tsx index 2d9910b9b..d1124794e 100644 --- a/web/containers/Providers/ClipboardListener.tsx +++ b/web/containers/Providers/ClipboardListener.tsx @@ -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 {children} + return } export default ClipboardListener diff --git a/web/containers/Providers/DataLoader.tsx b/web/containers/Providers/DataLoader.tsx index 245c254ac..470294996 100644 --- a/web/containers/Providers/DataLoader.tsx +++ b/web/containers/Providers/DataLoader.tsx @@ -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 = ({ 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 = ({ children }) => { console.debug('Load Data...') - return {children} + return } export default DataLoader diff --git a/web/containers/Providers/DeepLinkListener.tsx b/web/containers/Providers/DeepLinkListener.tsx index d5941204f..b991996a2 100644 --- a/web/containers/Providers/DeepLinkListener.tsx +++ b/web/containers/Providers/DeepLinkListener.tsx @@ -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 = ({ children }) => { +const DeepLinkListener: React.FC = () => { const { getHfRepoData } = useGetHFRepoData() const setLoadingInfo = useSetAtom(loadingModalInfoAtom) const setImportingHuggingFaceRepoData = useSetAtom( @@ -69,7 +66,7 @@ const DeepLinkListener: React.FC = ({ children }) => { handleDeepLinkAction(action) }) - return {children} + return } type DeepLinkAction = { diff --git a/web/containers/Providers/EventListener.tsx b/web/containers/Providers/EventListener.tsx index c1dcf7c40..bad1afda9 100644 --- a/web/containers/Providers/EventListener.tsx +++ b/web/containers/Providers/EventListener.tsx @@ -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 ( - - - - - {children} - - - - + <> + + + + + + ) } -export default EventListenerWrapper +export default EventListener diff --git a/web/containers/Providers/Jotai.tsx b/web/containers/Providers/Jotai.tsx index eee24a43a..c68226fef 100644 --- a/web/containers/Providers/Jotai.tsx +++ b/web/containers/Providers/Jotai.tsx @@ -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('') export const currentPromptAtom = atom('') export const fileUploadAtom = atom([]) @@ -16,7 +12,7 @@ export const searchAtom = atom('') export const selectedTextAtom = atom('') -export default function JotaiWrapper({ children }: Props) { +export default function JotaiWrapper({ children }: PropsWithChildren) { return {children} } diff --git a/web/containers/Providers/KeyListener.tsx b/web/containers/Providers/KeyListener.tsx index 6d09f026d..02a1d4eb5 100644 --- a/web/containers/Providers/KeyListener.tsx +++ b/web/containers/Providers/KeyListener.tsx @@ -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 {children} + return } diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/ModelHandler.tsx similarity index 98% rename from web/containers/Providers/EventHandler.tsx rename to web/containers/Providers/ModelHandler.tsx index b51468099..5f0951459 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/ModelHandler.tsx @@ -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 {children} + return } diff --git a/web/containers/Providers/ModelImportListener.tsx b/web/containers/Providers/ModelImportListener.tsx index a60b7be80..e99b1e6fc 100644 --- a/web/containers/Providers/ModelImportListener.tsx +++ b/web/containers/Providers/ModelImportListener.tsx @@ -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 {children} + return } export default ModelImportListener diff --git a/web/containers/Providers/QuickAskListener.tsx b/web/containers/Providers/QuickAskListener.tsx index 415fc19a6..03d685953 100644 --- a/web/containers/Providers/QuickAskListener.tsx +++ b/web/containers/Providers/QuickAskListener.tsx @@ -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 = ({ children }) => { +const QuickAskListener: React.FC = () => { const { sendChatMessage } = useSendChatMessage() const setMainState = useSetAtom(mainViewStateAtom) @@ -27,7 +23,7 @@ const QuickAskListener: React.FC = ({ children }) => { debounced(input) }) - return {children} + return } export default QuickAskListener diff --git a/web/containers/Providers/Responsive.test.tsx b/web/containers/Providers/Responsive.test.tsx index e72a5e7e6..5a03acea2 100644 --- a/web/containers/Providers/Responsive.test.tsx +++ b/web/containers/Providers/Responsive.test.tsx @@ -45,17 +45,6 @@ describe('Responsive', () => { }) }) - it('renders children correctly', () => { - const { getByText } = render( - -
Child Content
-
- ) - - // Check if the child content is rendered - expect(getByText('Child Content')).toBeInTheDocument() - }) - it('hides left and right panels on small screens', () => { // Simulate mobile view window.matchMedia = jest.fn().mockImplementation((query) => ({ @@ -64,11 +53,7 @@ describe('Responsive', () => { removeListener: jest.fn(), })) - render( - -
Child Content
-
- ) + render() // Check that the left and right panel states were updated to false expect(mockSetShowLeftPanel).toHaveBeenCalledWith(false) @@ -83,11 +68,7 @@ describe('Responsive', () => { removeListener: jest.fn(), })) - render( - -
Child Content
-
- ) + render() // Change back to desktop view window.matchMedia = jest.fn().mockImplementation((query) => ({ @@ -97,11 +78,7 @@ describe('Responsive', () => { })) // Call the effect manually to simulate the component re-rendering - const rerender = render( - -
Child Content
-
- ) + const rerender = render() // Check that the last known states were restored (which were true initially) expect(mockSetShowLeftPanel).toHaveBeenCalledWith(true) diff --git a/web/containers/Providers/Responsive.tsx b/web/containers/Providers/Responsive.tsx index 940cb68fb..cb7bd4c1c 100644 --- a/web/containers/Providers/Responsive.tsx +++ b/web/containers/Providers/Responsive.tsx @@ -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 {children} + return } export default Responsive diff --git a/web/containers/Providers/SettingsHandler.tsx b/web/containers/Providers/SettingsHandler.tsx new file mode 100644 index 000000000..0ec34b0d4 --- /dev/null +++ b/web/containers/Providers/SettingsHandler.tsx @@ -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 diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx index 4731c600b..f9e240b94 100644 --- a/web/containers/Providers/index.tsx +++ b/web/containers/Providers/index.tsx @@ -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 && } {setupCore && activated && ( <> - - - - - {children} - - - - - + + + + + + + + {children} )} diff --git a/web/hooks/useConfigurations.ts b/web/hooks/useConfigurations.ts new file mode 100644 index 000000000..9d4999b2f --- /dev/null +++ b/web/hooks/useConfigurations.ts @@ -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(ExtensionTypeEnum.Model) + ?.configurePullOptions( + proxyEnabled + ? { + proxy_url: proxyUrl, + verify_peer_ssl: !proxyIgnoreSSL, + } + : { + proxy_url: '', + verify_peer_ssl: false, + } + ) + }, [proxyEnabled, proxyUrl, proxyIgnoreSSL]) + + useEffect(() => { + configurePullOptions() + }, []) + + return { + configurePullOptions, + } +} diff --git a/web/hooks/useModels.test.ts b/web/hooks/useModels.test.ts index e848c455c..331dfd67b 100644 --- a/web/hooks/useModels.test.ts +++ b/web/hooks/useModels.test.ts @@ -43,7 +43,7 @@ describe('useModels', () => { const { result } = renderHook(() => useModels()) await act(() => { - result.current?.loadDataModel() + result.current?.getData() }) expect(mockModelExtension.getModels).toHaveBeenCalled() @@ -70,7 +70,7 @@ describe('useModels', () => { const { result } = renderHook(() => useModels()) await act(() => { - result.current?.loadDataModel() + result.current?.getData() }) expect(mockModelExtension.getModels()).rejects.toThrow() diff --git a/web/hooks/useModels.ts b/web/hooks/useModels.ts index ef15de763..0daedf7f8 100644 --- a/web/hooks/useModels.ts +++ b/web/hooks/useModels.ts @@ -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 => extensionManager @@ -118,25 +110,8 @@ const useModels = () => { } }, [reloadData, updateStates]) - const configurePullOptions = useCallback(() => { - extensionManager - .get(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, } } diff --git a/web/screens/Settings/Advanced/index.tsx b/web/screens/Settings/Advanced/index.tsx index c6125500d..8d791694c 100644 --- a/web/screens/Settings/Advanced/index.tsx +++ b/web/screens/Settings/Advanced/index.tsx @@ -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( null ) - const { configurePullOptions } = useModels() + const { configurePullOptions } = useConfigurations() const [toggle, setToggle] = useState(null)