Merge pull request #4141 from janhq/fix/wrong-component-wrapping-cause-excessive-app-rerender
fix: wrong component wrapping and state handling cause excessive app re-render
This commit is contained in:
commit
8642588694
@ -52,9 +52,8 @@ export default function RootLayout() {
|
|||||||
<body className="font-sans antialiased">
|
<body className="font-sans antialiased">
|
||||||
<JotaiWrapper>
|
<JotaiWrapper>
|
||||||
<ThemeWrapper>
|
<ThemeWrapper>
|
||||||
<ClipboardListener>
|
<ClipboardListener />
|
||||||
<Search />
|
<Search />
|
||||||
</ClipboardListener>
|
|
||||||
</ThemeWrapper>
|
</ThemeWrapper>
|
||||||
</JotaiWrapper>
|
</JotaiWrapper>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import { ReactNode, useCallback, useEffect, useRef } from 'react'
|
import { PropsWithChildren, useCallback, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import { ScrollArea } from '@janhq/joi'
|
import { ScrollArea } from '@janhq/joi'
|
||||||
|
|
||||||
type Props = {
|
const ListContainer = ({ children }: PropsWithChildren) => {
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const ListContainer = ({ children }: Props) => {
|
|
||||||
const listRef = useRef<HTMLDivElement>(null)
|
const listRef = useRef<HTMLDivElement>(null)
|
||||||
const prevScrollTop = useRef(0)
|
const prevScrollTop = useRef(0)
|
||||||
const isUserManuallyScrollingUp = useRef(false)
|
const isUserManuallyScrollingUp = useRef(false)
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import '@testing-library/jest-dom'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render, fireEvent } from '@testing-library/react'
|
import { render, fireEvent } from '@testing-library/react'
|
||||||
import ModelConfigInput from './index'
|
import ModelConfigInput from './index'
|
||||||
import { Tooltip } from '@janhq/joi'
|
|
||||||
|
|
||||||
// Mocking the Tooltip component to simplify testing
|
// Mocking the Tooltip component to simplify testing
|
||||||
jest.mock('@janhq/joi', () => ({
|
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 { AppUpdateInfo } from '@janhq/core'
|
||||||
import { useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
@ -8,7 +8,7 @@ import {
|
|||||||
updateVersionErrorAtom,
|
updateVersionErrorAtom,
|
||||||
} from '@/helpers/atoms/App.atom'
|
} from '@/helpers/atoms/App.atom'
|
||||||
|
|
||||||
const AppUpdateListener = ({ children }: PropsWithChildren) => {
|
const AppUpdateListener = () => {
|
||||||
const setProgress = useSetAtom(appDownloadProgressAtom)
|
const setProgress = useSetAtom(appDownloadProgressAtom)
|
||||||
const setUpdateVersionError = useSetAtom(updateVersionErrorAtom)
|
const setUpdateVersionError = useSetAtom(updateVersionErrorAtom)
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ const AppUpdateListener = ({ children }: PropsWithChildren) => {
|
|||||||
}
|
}
|
||||||
}, [setProgress, setUpdateVersionError])
|
}, [setProgress, setUpdateVersionError])
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment></Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppUpdateListener
|
export default AppUpdateListener
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Fragment, PropsWithChildren } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
|
||||||
import { useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { selectedTextAtom } from './Jotai'
|
import { selectedTextAtom } from './Jotai'
|
||||||
|
|
||||||
const ClipboardListener = ({ children }: PropsWithChildren) => {
|
const ClipboardListener = () => {
|
||||||
const setSelectedText = useSetAtom(selectedTextAtom)
|
const setSelectedText = useSetAtom(selectedTextAtom)
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@ -13,7 +13,7 @@ const ClipboardListener = ({ children }: PropsWithChildren) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment></Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ClipboardListener
|
export default ClipboardListener
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Fragment, ReactNode, useEffect } from 'react'
|
import { Fragment, useEffect } from 'react'
|
||||||
|
|
||||||
import { AppConfiguration, getUserHomePath } from '@janhq/core'
|
import { AppConfiguration, getUserHomePath } from '@janhq/core'
|
||||||
import { useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import useAssistants from '@/hooks/useAssistants'
|
import useAssistants from '@/hooks/useAssistants'
|
||||||
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
||||||
import { useLoadTheme } from '@/hooks/useLoadTheme'
|
|
||||||
import useModels from '@/hooks/useModels'
|
import useModels from '@/hooks/useModels'
|
||||||
import useThreads from '@/hooks/useThreads'
|
import useThreads from '@/hooks/useThreads'
|
||||||
|
|
||||||
@ -20,27 +19,20 @@ import {
|
|||||||
} from '@/helpers/atoms/AppConfig.atom'
|
} from '@/helpers/atoms/AppConfig.atom'
|
||||||
import { janSettingScreenAtom } from '@/helpers/atoms/Setting.atom'
|
import { janSettingScreenAtom } from '@/helpers/atoms/Setting.atom'
|
||||||
|
|
||||||
type Props = {
|
const DataLoader: React.FC = () => {
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const DataLoader: React.FC<Props> = ({ children }) => {
|
|
||||||
const setJanDataFolderPath = useSetAtom(janDataFolderPathAtom)
|
const setJanDataFolderPath = useSetAtom(janDataFolderPathAtom)
|
||||||
const setQuickAskEnabled = useSetAtom(quickAskEnabledAtom)
|
const setQuickAskEnabled = useSetAtom(quickAskEnabledAtom)
|
||||||
const setJanDefaultDataFolder = useSetAtom(defaultJanDataFolderAtom)
|
const setJanDefaultDataFolder = useSetAtom(defaultJanDataFolderAtom)
|
||||||
const setJanSettingScreen = useSetAtom(janSettingScreenAtom)
|
const setJanSettingScreen = useSetAtom(janSettingScreenAtom)
|
||||||
const { loadDataModel, configurePullOptions } = useModels()
|
const { getData: loadModels } = useModels()
|
||||||
|
|
||||||
useThreads()
|
useThreads()
|
||||||
useAssistants()
|
useAssistants()
|
||||||
useGetSystemResources()
|
useGetSystemResources()
|
||||||
useLoadTheme()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load data once
|
// Load data once
|
||||||
loadDataModel()
|
loadModels()
|
||||||
// Configure pull options once
|
|
||||||
configurePullOptions()
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -71,7 +63,7 @@ const DataLoader: React.FC<Props> = ({ children }) => {
|
|||||||
|
|
||||||
console.debug('Load Data...')
|
console.debug('Load Data...')
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment></Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DataLoader
|
export default DataLoader
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Fragment, ReactNode } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
|
||||||
import { useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
@ -13,11 +13,8 @@ import {
|
|||||||
importHuggingFaceModelStageAtom,
|
importHuggingFaceModelStageAtom,
|
||||||
importingHuggingFaceRepoDataAtom,
|
importingHuggingFaceRepoDataAtom,
|
||||||
} from '@/helpers/atoms/HuggingFace.atom'
|
} from '@/helpers/atoms/HuggingFace.atom'
|
||||||
type Props = {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const DeepLinkListener: React.FC<Props> = ({ children }) => {
|
const DeepLinkListener: React.FC = () => {
|
||||||
const { getHfRepoData } = useGetHFRepoData()
|
const { getHfRepoData } = useGetHFRepoData()
|
||||||
const setLoadingInfo = useSetAtom(loadingModalInfoAtom)
|
const setLoadingInfo = useSetAtom(loadingModalInfoAtom)
|
||||||
const setImportingHuggingFaceRepoData = useSetAtom(
|
const setImportingHuggingFaceRepoData = useSetAtom(
|
||||||
@ -69,7 +66,7 @@ const DeepLinkListener: React.FC<Props> = ({ children }) => {
|
|||||||
handleDeepLinkAction(action)
|
handleDeepLinkAction(action)
|
||||||
})
|
})
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment></Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeepLinkAction = {
|
type DeepLinkAction = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { PropsWithChildren, useCallback, useEffect } from 'react'
|
import { useCallback, useEffect } from 'react'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ import { toaster } from '../Toast'
|
|||||||
|
|
||||||
import AppUpdateListener from './AppUpdateListener'
|
import AppUpdateListener from './AppUpdateListener'
|
||||||
import ClipboardListener from './ClipboardListener'
|
import ClipboardListener from './ClipboardListener'
|
||||||
import EventHandler from './EventHandler'
|
import ModelHandler from './ModelHandler'
|
||||||
|
|
||||||
import ModelImportListener from './ModelImportListener'
|
import ModelImportListener from './ModelImportListener'
|
||||||
import QuickAskListener from './QuickAskListener'
|
import QuickAskListener from './QuickAskListener'
|
||||||
@ -39,7 +39,7 @@ import {
|
|||||||
removeDownloadingModelAtom,
|
removeDownloadingModelAtom,
|
||||||
} from '@/helpers/atoms/Model.atom'
|
} from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const EventListenerWrapper = ({ children }: PropsWithChildren) => {
|
const EventListener = () => {
|
||||||
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
||||||
const setInstallingExtension = useSetAtom(setInstallingExtensionAtom)
|
const setInstallingExtension = useSetAtom(setInstallingExtensionAtom)
|
||||||
const removeInstallingExtension = useSetAtom(removeInstallingExtensionAtom)
|
const removeInstallingExtension = useSetAtom(removeInstallingExtensionAtom)
|
||||||
@ -156,16 +156,14 @@ const EventListenerWrapper = ({ children }: PropsWithChildren) => {
|
|||||||
])
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppUpdateListener>
|
<>
|
||||||
<ClipboardListener>
|
<AppUpdateListener />
|
||||||
<ModelImportListener>
|
<ClipboardListener />
|
||||||
<QuickAskListener>
|
<ModelImportListener />
|
||||||
<EventHandler>{children}</EventHandler>
|
<QuickAskListener />
|
||||||
</QuickAskListener>
|
<ModelHandler />
|
||||||
</ModelImportListener>
|
</>
|
||||||
</ClipboardListener>
|
|
||||||
</AppUpdateListener>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default EventListenerWrapper
|
export default EventListener
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ReactNode } from 'react'
|
import { PropsWithChildren } from 'react'
|
||||||
|
|
||||||
import { Provider, atom } from 'jotai'
|
import { Provider, atom } from 'jotai'
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export const editPromptAtom = atom<string>('')
|
export const editPromptAtom = atom<string>('')
|
||||||
export const currentPromptAtom = atom<string>('')
|
export const currentPromptAtom = atom<string>('')
|
||||||
export const fileUploadAtom = atom<FileInfo[]>([])
|
export const fileUploadAtom = atom<FileInfo[]>([])
|
||||||
@ -16,7 +12,7 @@ export const searchAtom = atom<string>('')
|
|||||||
|
|
||||||
export const selectedTextAtom = atom('')
|
export const selectedTextAtom = atom('')
|
||||||
|
|
||||||
export default function JotaiWrapper({ children }: Props) {
|
export default function JotaiWrapper({ children }: PropsWithChildren) {
|
||||||
return <Provider>{children}</Provider>
|
return <Provider>{children}</Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { Fragment, ReactNode, useEffect } from 'react'
|
import { Fragment, useEffect } from 'react'
|
||||||
|
|
||||||
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
||||||
|
|
||||||
@ -22,11 +22,7 @@ import {
|
|||||||
ThreadModalAction,
|
ThreadModalAction,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
type Props = {
|
export default function KeyListener() {
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function KeyListener({ children }: Props) {
|
|
||||||
const setShowLeftPanel = useSetAtom(showLeftPanelAtom)
|
const setShowLeftPanel = useSetAtom(showLeftPanelAtom)
|
||||||
const setShowRightPanel = useSetAtom(showRightPanelAtom)
|
const setShowRightPanel = useSetAtom(showRightPanelAtom)
|
||||||
const [mainViewState, setMainViewState] = useAtom(mainViewStateAtom)
|
const [mainViewState, setMainViewState] = useAtom(mainViewStateAtom)
|
||||||
@ -94,5 +90,5 @@ export default function KeyListener({ children }: Props) {
|
|||||||
setShowRightPanel,
|
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 {
|
import {
|
||||||
ChatCompletionMessage,
|
ChatCompletionMessage,
|
||||||
@ -43,7 +43,7 @@ import {
|
|||||||
const maxWordForThreadTitle = 10
|
const maxWordForThreadTitle = 10
|
||||||
const defaultThreadTitle = 'New Thread'
|
const defaultThreadTitle = 'New Thread'
|
||||||
|
|
||||||
export default function EventHandler({ children }: { children: ReactNode }) {
|
export default function ModelHandler() {
|
||||||
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||||
const addNewMessage = useSetAtom(addNewMessageAtom)
|
const addNewMessage = useSetAtom(addNewMessageAtom)
|
||||||
const updateMessage = useSetAtom(updateMessageAtom)
|
const updateMessage = useSetAtom(updateMessageAtom)
|
||||||
@ -333,5 +333,5 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
}, [onNewMessageResponse, onMessageResponseUpdate, onModelStopped])
|
}, [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 {
|
import {
|
||||||
ImportingModel,
|
ImportingModel,
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
updateImportingModelProgressAtom,
|
updateImportingModelProgressAtom,
|
||||||
} from '@/helpers/atoms/Model.atom'
|
} from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const ModelImportListener = ({ children }: PropsWithChildren) => {
|
const ModelImportListener = () => {
|
||||||
const updateImportingModelProgress = useSetAtom(
|
const updateImportingModelProgress = useSetAtom(
|
||||||
updateImportingModelProgressAtom
|
updateImportingModelProgressAtom
|
||||||
)
|
)
|
||||||
@ -103,7 +103,7 @@ const ModelImportListener = ({ children }: PropsWithChildren) => {
|
|||||||
onImportModelFailed,
|
onImportModelFailed,
|
||||||
])
|
])
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment></Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ModelImportListener
|
export default ModelImportListener
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Fragment, ReactNode } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
|
||||||
import { useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
@ -10,11 +10,7 @@ import useSendChatMessage from '@/hooks/useSendChatMessage'
|
|||||||
|
|
||||||
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
|
||||||
|
|
||||||
type Props = {
|
const QuickAskListener: React.FC = () => {
|
||||||
children: ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const QuickAskListener: React.FC<Props> = ({ children }) => {
|
|
||||||
const { sendChatMessage } = useSendChatMessage()
|
const { sendChatMessage } = useSendChatMessage()
|
||||||
const setMainState = useSetAtom(mainViewStateAtom)
|
const setMainState = useSetAtom(mainViewStateAtom)
|
||||||
|
|
||||||
@ -27,7 +23,7 @@ const QuickAskListener: React.FC<Props> = ({ children }) => {
|
|||||||
debounced(input)
|
debounced(input)
|
||||||
})
|
})
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment></Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QuickAskListener
|
export default QuickAskListener
|
||||||
|
|||||||
@ -45,17 +45,6 @@ describe('Responsive', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders children correctly', () => {
|
|
||||||
const { getByText } = render(
|
|
||||||
<Responsive>
|
|
||||||
<div>Child Content</div>
|
|
||||||
</Responsive>
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if the child content is rendered
|
|
||||||
expect(getByText('Child Content')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('hides left and right panels on small screens', () => {
|
it('hides left and right panels on small screens', () => {
|
||||||
// Simulate mobile view
|
// Simulate mobile view
|
||||||
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
||||||
@ -64,11 +53,7 @@ describe('Responsive', () => {
|
|||||||
removeListener: jest.fn(),
|
removeListener: jest.fn(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
render(
|
render(<Responsive />)
|
||||||
<Responsive>
|
|
||||||
<div>Child Content</div>
|
|
||||||
</Responsive>
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check that the left and right panel states were updated to false
|
// Check that the left and right panel states were updated to false
|
||||||
expect(mockSetShowLeftPanel).toHaveBeenCalledWith(false)
|
expect(mockSetShowLeftPanel).toHaveBeenCalledWith(false)
|
||||||
@ -83,11 +68,7 @@ describe('Responsive', () => {
|
|||||||
removeListener: jest.fn(),
|
removeListener: jest.fn(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
render(
|
render(<Responsive />)
|
||||||
<Responsive>
|
|
||||||
<div>Child Content</div>
|
|
||||||
</Responsive>
|
|
||||||
)
|
|
||||||
|
|
||||||
// Change back to desktop view
|
// Change back to desktop view
|
||||||
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
window.matchMedia = jest.fn().mockImplementation((query) => ({
|
||||||
@ -97,11 +78,7 @@ describe('Responsive', () => {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Call the effect manually to simulate the component re-rendering
|
// Call the effect manually to simulate the component re-rendering
|
||||||
const rerender = render(
|
const rerender = render(<Responsive />)
|
||||||
<Responsive>
|
|
||||||
<div>Child Content</div>
|
|
||||||
</Responsive>
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check that the last known states were restored (which were true initially)
|
// Check that the last known states were restored (which were true initially)
|
||||||
expect(mockSetShowLeftPanel).toHaveBeenCalledWith(true)
|
expect(mockSetShowLeftPanel).toHaveBeenCalledWith(true)
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Fragment, PropsWithChildren, useEffect, useRef } from 'react'
|
import { Fragment, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import { useMediaQuery } from '@janhq/joi'
|
import { useMediaQuery } from '@janhq/joi'
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom } from 'jotai'
|
||||||
|
|
||||||
import { showLeftPanelAtom, showRightPanelAtom } from '@/helpers/atoms/App.atom'
|
import { showLeftPanelAtom, showRightPanelAtom } from '@/helpers/atoms/App.atom'
|
||||||
|
|
||||||
const Responsive = ({ children }: PropsWithChildren) => {
|
const Responsive = () => {
|
||||||
const matches = useMediaQuery('(max-width: 880px)')
|
const matches = useMediaQuery('(max-width: 880px)')
|
||||||
const [showLeftPanel, setShowLeftPanel] = useAtom(showLeftPanelAtom)
|
const [showLeftPanel, setShowLeftPanel] = useAtom(showLeftPanelAtom)
|
||||||
const [showRightPanel, setShowRightPanel] = useAtom(showRightPanelAtom)
|
const [showRightPanel, setShowRightPanel] = useAtom(showRightPanelAtom)
|
||||||
@ -30,7 +30,7 @@ const Responsive = ({ children }: PropsWithChildren) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [matches, setShowLeftPanel, setShowRightPanel])
|
}, [matches, setShowLeftPanel, setShowRightPanel])
|
||||||
|
|
||||||
return <Fragment>{children}</Fragment>
|
return <Fragment></Fragment>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Responsive
|
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 { Toaster } from 'react-hot-toast'
|
||||||
|
|
||||||
import Loader from '@/containers/Loader'
|
import Loader from '@/containers/Loader'
|
||||||
import EventListenerWrapper from '@/containers/Providers/EventListener'
|
import EventListener from '@/containers/Providers/EventListener'
|
||||||
import JotaiWrapper from '@/containers/Providers/Jotai'
|
import JotaiWrapper from '@/containers/Providers/Jotai'
|
||||||
|
|
||||||
import ThemeWrapper from '@/containers/Providers/Theme'
|
import ThemeWrapper from '@/containers/Providers/Theme'
|
||||||
@ -24,6 +24,8 @@ import DeepLinkListener from './DeepLinkListener'
|
|||||||
import KeyListener from './KeyListener'
|
import KeyListener from './KeyListener'
|
||||||
import Responsive from './Responsive'
|
import Responsive from './Responsive'
|
||||||
|
|
||||||
|
import SettingsHandler from './SettingsHandler'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension'
|
import { extensionManager } from '@/extension'
|
||||||
|
|
||||||
const Providers = ({ children }: PropsWithChildren) => {
|
const Providers = ({ children }: PropsWithChildren) => {
|
||||||
@ -76,16 +78,14 @@ const Providers = ({ children }: PropsWithChildren) => {
|
|||||||
{settingUp && <Loader description="Preparing Update..." />}
|
{settingUp && <Loader description="Preparing Update..." />}
|
||||||
{setupCore && activated && (
|
{setupCore && activated && (
|
||||||
<>
|
<>
|
||||||
<Responsive>
|
<Responsive />
|
||||||
<KeyListener>
|
<KeyListener />
|
||||||
<EventListenerWrapper>
|
<EventListener />
|
||||||
<DataLoader>
|
<DataLoader />
|
||||||
<DeepLinkListener>{children}</DeepLinkListener>
|
<SettingsHandler />
|
||||||
</DataLoader>
|
<DeepLinkListener />
|
||||||
</EventListenerWrapper>
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</KeyListener>
|
{children}
|
||||||
</Responsive>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</JotaiWrapper>
|
</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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -43,7 +43,7 @@ describe('useModels', () => {
|
|||||||
|
|
||||||
const { result } = renderHook(() => useModels())
|
const { result } = renderHook(() => useModels())
|
||||||
await act(() => {
|
await act(() => {
|
||||||
result.current?.loadDataModel()
|
result.current?.getData()
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockModelExtension.getModels).toHaveBeenCalled()
|
expect(mockModelExtension.getModels).toHaveBeenCalled()
|
||||||
@ -70,7 +70,7 @@ describe('useModels', () => {
|
|||||||
const { result } = renderHook(() => useModels())
|
const { result } = renderHook(() => useModels())
|
||||||
|
|
||||||
await act(() => {
|
await act(() => {
|
||||||
result.current?.loadDataModel()
|
result.current?.getData()
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockModelExtension.getModels()).rejects.toThrow()
|
expect(mockModelExtension.getModels()).rejects.toThrow()
|
||||||
|
|||||||
@ -9,18 +9,14 @@ import {
|
|||||||
ModelManager,
|
ModelManager,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
|
|
||||||
import { useSetAtom, useAtom, useAtomValue } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
|
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
|
|
||||||
import { isLocalEngine } from '@/utils/modelEngine'
|
import { isLocalEngine } from '@/utils/modelEngine'
|
||||||
|
|
||||||
import { extensionManager } from '@/extension'
|
import { extensionManager } from '@/extension'
|
||||||
import {
|
|
||||||
ignoreSslAtom,
|
|
||||||
proxyAtom,
|
|
||||||
proxyEnabledAtom,
|
|
||||||
} from '@/helpers/atoms/AppConfig.atom'
|
|
||||||
import {
|
import {
|
||||||
configuredModelsAtom,
|
configuredModelsAtom,
|
||||||
downloadedModelsAtom,
|
downloadedModelsAtom,
|
||||||
@ -32,11 +28,8 @@ import {
|
|||||||
* and updates the atoms accordingly.
|
* and updates the atoms accordingly.
|
||||||
*/
|
*/
|
||||||
const useModels = () => {
|
const useModels = () => {
|
||||||
const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelsAtom)
|
const setDownloadedModels = useSetAtom(downloadedModelsAtom)
|
||||||
const setExtensionModels = useSetAtom(configuredModelsAtom)
|
const setExtensionModels = useSetAtom(configuredModelsAtom)
|
||||||
const proxyEnabled = useAtomValue(proxyEnabledAtom)
|
|
||||||
const proxyUrl = useAtomValue(proxyAtom)
|
|
||||||
const proxyIgnoreSSL = useAtomValue(ignoreSslAtom)
|
|
||||||
|
|
||||||
const getData = useCallback(() => {
|
const getData = useCallback(() => {
|
||||||
const getDownloadedModels = async () => {
|
const getDownloadedModels = async () => {
|
||||||
@ -87,18 +80,17 @@ const useModels = () => {
|
|||||||
|
|
||||||
const updateStates = useCallback(() => {
|
const updateStates = useCallback(() => {
|
||||||
const cachedModels = ModelManager.instance().models.values().toArray()
|
const cachedModels = ModelManager.instance().models.values().toArray()
|
||||||
const toUpdate = [
|
setDownloadedModels((downloadedModels) => [
|
||||||
...downloadedModels,
|
...downloadedModels,
|
||||||
...cachedModels.filter(
|
...cachedModels.filter(
|
||||||
(e) =>
|
(e) =>
|
||||||
!isLocalEngine(e.engine) &&
|
!isLocalEngine(e.engine) &&
|
||||||
!downloadedModels.some((g: Model) => g.id === e.id)
|
!downloadedModels.some((g: Model) => g.id === e.id)
|
||||||
),
|
),
|
||||||
]
|
])
|
||||||
|
|
||||||
setDownloadedModels(toUpdate)
|
|
||||||
setExtensionModels(cachedModels)
|
setExtensionModels(cachedModels)
|
||||||
}, [downloadedModels, setDownloadedModels, setExtensionModels])
|
}, [setDownloadedModels, setExtensionModels])
|
||||||
|
|
||||||
const getModels = async (): Promise<Model[]> =>
|
const getModels = async (): Promise<Model[]> =>
|
||||||
extensionManager
|
extensionManager
|
||||||
@ -118,25 +110,8 @@ const useModels = () => {
|
|||||||
}
|
}
|
||||||
}, [reloadData, updateStates])
|
}, [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 {
|
return {
|
||||||
loadDataModel: getData,
|
getData,
|
||||||
configurePullOptions,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { useDebouncedCallback } from 'use-debounce'
|
|||||||
import { snackbar, toaster } from '@/containers/Toast'
|
import { snackbar, toaster } from '@/containers/Toast'
|
||||||
|
|
||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
import useModels from '@/hooks/useModels'
|
import { useConfigurations } from '@/hooks/useConfigurations'
|
||||||
import { useSettings } from '@/hooks/useSettings'
|
import { useSettings } from '@/hooks/useSettings'
|
||||||
|
|
||||||
import DataFolder from './DataFolder'
|
import DataFolder from './DataFolder'
|
||||||
@ -68,7 +68,7 @@ const Advanced = () => {
|
|||||||
const [dropdownOptions, setDropdownOptions] = useState<HTMLDivElement | null>(
|
const [dropdownOptions, setDropdownOptions] = useState<HTMLDivElement | null>(
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
const { configurePullOptions } = useModels()
|
const { configurePullOptions } = useConfigurations()
|
||||||
|
|
||||||
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user