diff --git a/web/containers/Layout/index.tsx b/web/containers/Layout/index.tsx
index 8a3f417f44..e787163d48 100644
--- a/web/containers/Layout/index.tsx
+++ b/web/containers/Layout/index.tsx
@@ -1,10 +1,8 @@
'use client'
-import { useEffect } from 'react'
+import { useEffect, useMemo } from 'react'
-import { motion as m } from 'framer-motion'
-
-import { useAtom, useAtomValue } from 'jotai'
+import { useAtomValue, useSetAtom } from 'jotai'
import { twMerge } from 'tailwind-merge'
@@ -36,7 +34,7 @@ import { mainViewStateAtom } from '@/helpers/atoms/App.atom'
import { reduceTransparentAtom } from '@/helpers/atoms/Setting.atom'
const BaseLayout = () => {
- const [mainViewState, setMainViewState] = useAtom(mainViewStateAtom)
+ const setMainViewState = useSetAtom(mainViewStateAtom)
const importModelStage = useAtomValue(getImportModelStageAtom)
const reduceTransparent = useAtomValue(reduceTransparentAtom)
@@ -68,24 +66,7 @@ const BaseLayout = () => {
-
+
{importModelStage === 'SELECTING_MODEL' &&
}
{importModelStage === 'MODEL_SELECTED' &&
}
diff --git a/web/containers/MainViewContainer/index.tsx b/web/containers/MainViewContainer/index.tsx
index 4f3b4986a9..ba7f87fd2b 100644
--- a/web/containers/MainViewContainer/index.tsx
+++ b/web/containers/MainViewContainer/index.tsx
@@ -1,5 +1,10 @@
+import { memo } from 'react'
+
+import { motion as m } from 'framer-motion'
import { useAtomValue } from 'jotai'
+import { twMerge } from 'tailwind-merge'
+
import { MainViewState } from '@/constants/screens'
import HubScreen from '@/screens/Hub'
@@ -31,7 +36,26 @@ const MainViewContainer = () => {
break
}
- return children
+ return (
+
+ )
}
-export default MainViewContainer
+export default memo(MainViewContainer)
diff --git a/web/containers/Providers/CoreConfigurator.tsx b/web/containers/Providers/CoreConfigurator.tsx
new file mode 100644
index 0000000000..8af31162de
--- /dev/null
+++ b/web/containers/Providers/CoreConfigurator.tsx
@@ -0,0 +1,64 @@
+'use client'
+
+import { PropsWithChildren, useCallback, useEffect, useState } from 'react'
+
+import Loader from '@/containers/Loader'
+
+import { setupCoreServices } from '@/services/coreService'
+import {
+ isCoreExtensionInstalled,
+ setupBaseExtensions,
+} from '@/services/extensionService'
+
+import { extensionManager } from '@/extension'
+
+export const CoreConfigurator = ({ children }: PropsWithChildren) => {
+ const [setupCore, setSetupCore] = useState(false)
+ const [activated, setActivated] = useState(false)
+ const [settingUp, setSettingUp] = useState(false)
+
+ const setupExtensions = useCallback(async () => {
+ // Register all active extensions
+ await extensionManager.registerActive()
+
+ setTimeout(async () => {
+ if (!isCoreExtensionInstalled()) {
+ setSettingUp(true)
+ await setupBaseExtensions()
+ return
+ }
+
+ extensionManager.load()
+ setSettingUp(false)
+ setActivated(true)
+ }, 500)
+ }, [])
+
+ // Services Setup
+ useEffect(() => {
+ setupCoreServices()
+ setSetupCore(true)
+ return () => {
+ extensionManager.unload()
+ }
+ }, [])
+
+ useEffect(() => {
+ if (setupCore) {
+ // Electron
+ if (window && window.core?.api) {
+ setupExtensions()
+ } else {
+ // Host
+ setActivated(true)
+ }
+ }
+ }, [setupCore, setupExtensions])
+
+ return (
+ <>
+ {settingUp &&
}
+ {setupCore && activated && <>{children}>}
+ >
+ )
+}
diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx
index f9e240b948..67778e30c4 100644
--- a/web/containers/Providers/index.tsx
+++ b/web/containers/Providers/index.tsx
@@ -1,23 +1,17 @@
'use client'
-import { PropsWithChildren, useCallback, useEffect, useState } from 'react'
+import { PropsWithChildren } from 'react'
import { Toaster } from 'react-hot-toast'
-import Loader from '@/containers/Loader'
import EventListener from '@/containers/Providers/EventListener'
import JotaiWrapper from '@/containers/Providers/Jotai'
import ThemeWrapper from '@/containers/Providers/Theme'
-import { setupCoreServices } from '@/services/coreService'
-import {
- isCoreExtensionInstalled,
- setupBaseExtensions,
-} from '@/services/extensionService'
-
import Umami from '@/utils/umami'
+import { CoreConfigurator } from './CoreConfigurator'
import DataLoader from './DataLoader'
import DeepLinkListener from './DeepLinkListener'
@@ -26,57 +20,12 @@ import Responsive from './Responsive'
import SettingsHandler from './SettingsHandler'
-import { extensionManager } from '@/extension'
-
const Providers = ({ children }: PropsWithChildren) => {
- const [setupCore, setSetupCore] = useState(false)
- const [activated, setActivated] = useState(false)
- const [settingUp, setSettingUp] = useState(false)
-
- const setupExtensions = useCallback(async () => {
- // Register all active extensions
- await extensionManager.registerActive()
-
- setTimeout(async () => {
- if (!isCoreExtensionInstalled()) {
- setSettingUp(true)
- await setupBaseExtensions()
- return
- }
-
- extensionManager.load()
- setSettingUp(false)
- setActivated(true)
- }, 500)
- }, [])
-
- // Services Setup
- useEffect(() => {
- setupCoreServices()
- setSetupCore(true)
- return () => {
- extensionManager.unload()
- }
- }, [])
-
- useEffect(() => {
- if (setupCore) {
- // Electron
- if (window && window.core?.api) {
- setupExtensions()
- } else {
- // Host
- setActivated(true)
- }
- }
- }, [setupCore, setupExtensions])
-
return (
- {settingUp && }
- {setupCore && activated && (
+
<>
@@ -87,7 +36,7 @@ const Providers = ({ children }: PropsWithChildren) => {
{children}
>
- )}
+
)
diff --git a/web/helpers/atoms/AppConfig.atom.ts b/web/helpers/atoms/AppConfig.atom.ts
index f4acc7dc22..68a375f3b2 100644
--- a/web/helpers/atoms/AppConfig.atom.ts
+++ b/web/helpers/atoms/AppConfig.atom.ts
@@ -12,14 +12,35 @@ export const janDataFolderPathAtom = atom('')
export const experimentalFeatureEnabledAtom = atomWithStorage(
EXPERIMENTAL_FEATURE,
- false
+ false,
+ undefined,
+ { getOnInit: true }
)
-export const proxyEnabledAtom = atomWithStorage(PROXY_FEATURE_ENABLED, false)
-export const proxyAtom = atomWithStorage(HTTPS_PROXY_FEATURE, '')
+export const proxyEnabledAtom = atomWithStorage(
+ PROXY_FEATURE_ENABLED,
+ false,
+ undefined,
+ { getOnInit: true }
+)
+export const proxyAtom = atomWithStorage(HTTPS_PROXY_FEATURE, '', undefined, {
+ getOnInit: true,
+})
-export const ignoreSslAtom = atomWithStorage(IGNORE_SSL, false)
-export const vulkanEnabledAtom = atomWithStorage(VULKAN_ENABLED, false)
-export const quickAskEnabledAtom = atomWithStorage(QUICK_ASK_ENABLED, false)
+export const ignoreSslAtom = atomWithStorage(IGNORE_SSL, false, undefined, {
+ getOnInit: true,
+})
+export const vulkanEnabledAtom = atomWithStorage(
+ VULKAN_ENABLED,
+ false,
+ undefined,
+ { getOnInit: true }
+)
+export const quickAskEnabledAtom = atomWithStorage(
+ QUICK_ASK_ENABLED,
+ false,
+ undefined,
+ { getOnInit: true }
+)
export const hostAtom = atom('http://localhost:1337/')
diff --git a/web/helpers/atoms/Model.atom.ts b/web/helpers/atoms/Model.atom.ts
index dd4414801a..445e36a4a8 100644
--- a/web/helpers/atoms/Model.atom.ts
+++ b/web/helpers/atoms/Model.atom.ts
@@ -16,7 +16,9 @@ enum ModelStorageAtomKeys {
*/
export const downloadedModelsAtom = atomWithStorage
(
ModelStorageAtomKeys.DownloadedModels,
- []
+ [],
+ undefined,
+ { getOnInit: true }
)
/**
@@ -25,7 +27,9 @@ export const downloadedModelsAtom = atomWithStorage(
*/
export const configuredModelsAtom = atomWithStorage(
ModelStorageAtomKeys.AvailableModels,
- []
+ [],
+ undefined,
+ { getOnInit: true }
)
export const removeDownloadedModelAtom = atom(
diff --git a/web/helpers/atoms/Setting.atom.ts b/web/helpers/atoms/Setting.atom.ts
index 57ca878541..904e85fe52 100644
--- a/web/helpers/atoms/Setting.atom.ts
+++ b/web/helpers/atoms/Setting.atom.ts
@@ -13,10 +13,22 @@ export const REDUCE_TRANSPARENT = 'reduceTransparent'
export const SPELL_CHECKING = 'spellChecking'
export const themesOptionsAtom = atom<{ name: string; value: string }[]>([])
export const janThemesPathAtom = atom(undefined)
-export const selectedThemeIdAtom = atomWithStorage(THEME, '')
+export const selectedThemeIdAtom = atomWithStorage(
+ THEME,
+ '',
+ undefined,
+ { getOnInit: true }
+)
export const themeDataAtom = atom(undefined)
export const reduceTransparentAtom = atomWithStorage(
REDUCE_TRANSPARENT,
- false
+ false,
+ undefined,
+ { getOnInit: true }
+)
+export const spellCheckAtom = atomWithStorage(
+ SPELL_CHECKING,
+ false,
+ undefined,
+ { getOnInit: true }
)
-export const spellCheckAtom = atomWithStorage(SPELL_CHECKING, false)
diff --git a/web/helpers/atoms/Thread.atom.ts b/web/helpers/atoms/Thread.atom.ts
index 1945fea45d..e0ea433ce7 100644
--- a/web/helpers/atoms/Thread.atom.ts
+++ b/web/helpers/atoms/Thread.atom.ts
@@ -207,7 +207,9 @@ export const setThreadModelParamsAtom = atom(
*/
export const activeSettingInputBoxAtom = atomWithStorage(
ACTIVE_SETTING_INPUT_BOX,
- false
+ false,
+ undefined,
+ { getOnInit: true }
)
/**
diff --git a/web/hooks/useStarterScreen.ts b/web/hooks/useStarterScreen.ts
index 4af19bd647..c551ee6019 100644
--- a/web/hooks/useStarterScreen.ts
+++ b/web/hooks/useStarterScreen.ts
@@ -1,22 +1,20 @@
-import { useState, useEffect } from 'react'
+import { useState, useEffect, useMemo } from 'react'
-import { useAtomValue, useSetAtom } from 'jotai'
+import { useAtomValue } from 'jotai'
import { isLocalEngine } from '@/utils/modelEngine'
import { extensionManager } from '@/extension'
-import {
- downloadedModelsAtom,
- selectedModelAtom,
-} from '@/helpers/atoms/Model.atom'
+import { downloadedModelsAtom } from '@/helpers/atoms/Model.atom'
import { threadsAtom } from '@/helpers/atoms/Thread.atom'
export function useStarterScreen() {
const downloadedModels = useAtomValue(downloadedModelsAtom)
const threads = useAtomValue(threadsAtom)
- const setSelectedModel = useSetAtom(selectedModelAtom)
- const isDownloadALocalModel = downloadedModels.some((x) =>
- isLocalEngine(x.engine)
+
+ const isDownloadALocalModel = useMemo(
+ () => downloadedModels.some((x) => isLocalEngine(x.engine)),
+ [downloadedModels]
)
const [extensionHasSettings, setExtensionHasSettings] = useState<
@@ -24,9 +22,6 @@ export function useStarterScreen() {
>([])
useEffect(() => {
- if (isDownloadALocalModel) {
- setSelectedModel(downloadedModels[0])
- }
const getAllSettings = async () => {
const extensionsMenu: {
name?: string
@@ -66,12 +61,16 @@ export function useStarterScreen() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
- const isAnyRemoteModelConfigured = extensionHasSettings.some(
- (x) => x.apiKey.length > 1
+ const isAnyRemoteModelConfigured = useMemo(
+ () => extensionHasSettings.some((x) => x.apiKey.length > 1),
+ [extensionHasSettings]
)
- const isShowStarterScreen =
- !isAnyRemoteModelConfigured && !isDownloadALocalModel && !threads.length
+ const isShowStarterScreen = useMemo(
+ () =>
+ !isAnyRemoteModelConfigured && !isDownloadALocalModel && !threads.length,
+ [isAnyRemoteModelConfigured, isDownloadALocalModel, threads]
+ )
return {
extensionHasSettings,
diff --git a/web/screens/Thread/ThreadCenterPanel/ChatBody/OnDeviceStarterScreen/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatBody/OnDeviceStarterScreen/index.tsx
index 5c042000a6..44d1748ed9 100644
--- a/web/screens/Thread/ThreadCenterPanel/ChatBody/OnDeviceStarterScreen/index.tsx
+++ b/web/screens/Thread/ThreadCenterPanel/ChatBody/OnDeviceStarterScreen/index.tsx
@@ -24,6 +24,8 @@ import useDownloadModel from '@/hooks/useDownloadModel'
import { modelDownloadStateAtom } from '@/hooks/useDownloadState'
+import { useStarterScreen } from '@/hooks/useStarterScreen'
+
import { formatDownloadPercentage, toGibibytes } from '@/utils/converter'
import {
getLogoEngine,
@@ -38,16 +40,8 @@ import {
} from '@/helpers/atoms/Model.atom'
import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom'
-type Props = {
- extensionHasSettings: {
- name?: string
- setting: string
- apiKey: string
- provider: string
- }[]
-}
-
-const OnDeviceStarterScreen = ({ extensionHasSettings }: Props) => {
+const OnDeviceStarterScreen = () => {
+ const { extensionHasSettings } = useStarterScreen()
const [searchValue, setSearchValue] = useState('')
const [isOpen, setIsOpen] = useState(Boolean(searchValue.length))
const downloadingModels = useAtomValue(getDownloadingModelAtom)
diff --git a/web/screens/Thread/ThreadCenterPanel/ChatBody/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatBody/index.tsx
index 6b3f4150a4..38af2cfc0d 100644
--- a/web/screens/Thread/ThreadCenterPanel/ChatBody/index.tsx
+++ b/web/screens/Thread/ThreadCenterPanel/ChatBody/index.tsx
@@ -1,3 +1,5 @@
+import { memo } from 'react'
+
import { MessageStatus } from '@janhq/core'
import { useAtomValue } from 'jotai'
@@ -44,4 +46,4 @@ const ChatBody = () => {
)
}
-export default ChatBody
+export default memo(ChatBody)
diff --git a/web/screens/Thread/ThreadCenterPanel/index.tsx b/web/screens/Thread/ThreadCenterPanel/index.tsx
index 1f23e9dc5b..01ba0aaeb5 100644
--- a/web/screens/Thread/ThreadCenterPanel/index.tsx
+++ b/web/screens/Thread/ThreadCenterPanel/index.tsx
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
-import { useEffect, useState } from 'react'
+import { memo, useEffect, useState } from 'react'
import { Accept, useDropzone } from 'react-dropzone'
@@ -232,4 +232,4 @@ const ThreadCenterPanel = () => {
)
}
-export default ThreadCenterPanel
+export default memo(ThreadCenterPanel)
diff --git a/web/screens/Thread/index.tsx b/web/screens/Thread/index.tsx
index b576c905cc..6789c181d4 100644
--- a/web/screens/Thread/index.tsx
+++ b/web/screens/Thread/index.tsx
@@ -1,3 +1,5 @@
+import { memo } from 'react'
+
import { useStarterScreen } from '@/hooks/useStarterScreen'
import ThreadLeftPanel from '@/screens/Thread/ThreadLeftPanel'
@@ -9,19 +11,31 @@ import ModalDeleteThread from './ThreadLeftPanel/ModalDeleteThread'
import ModalEditTitleThread from './ThreadLeftPanel/ModalEditTitleThread'
import ThreadRightPanel from './ThreadRightPanel'
+type Props = {
+ isShowStarterScreen: boolean
+}
+
+const ThreadPanels = memo(({ isShowStarterScreen }: Props) => {
+ return isShowStarterScreen ? (
+
+ ) : (
+ <>
+
+
+
+ >
+ )
+})
+
+const WelcomeController = () => {
+ const { isShowStarterScreen } = useStarterScreen()
+ return
+}
+
const ThreadScreen = () => {
- const { extensionHasSettings, isShowStarterScreen } = useStarterScreen()
return (
- {isShowStarterScreen ? (
-
- ) : (
- <>
-
-
-
- >
- )}
+
{/* Showing variant modal action for thread screen */}
@@ -31,4 +45,4 @@ const ThreadScreen = () => {
)
}
-export default ThreadScreen
+export default memo(ThreadScreen)