cherry-studio/src/renderer/src/hooks/useAppInit.ts
beyondkmp 871565c687
feat: add data limit warning notification (#8866)
* feat: add disk space checking functionality

- Introduced a new IPC channel for retrieving disk information.
- Integrated the 'check-disk-space' package to fetch available and total disk space.
- Updated the preload API to expose the new disk info retrieval method to the renderer.

* feat: implement disk space warning and data limit checks

- Added functionality to check available disk space and display warnings when storage is low.
- Updated IPC methods to pass directory paths for disk info retrieval.
- Introduced periodic checks for app data disk quota and internal storage quota.
- Enhanced user notifications with localized messages for low storage warnings.

* fix: enhance disk space warning logic and improve logging

- Added additional conditions for displaying disk space warnings based on free percentage thresholds.
- Improved logging format for app data disk quota, providing clearer output in GB.
- Refactored the checkDataLimit function to be asynchronous for better performance.

* format code

* update log format

* fix: improve error handling and logging in disk quota checks

- Added try-catch block in checkAppDataDiskQuota to handle potential errors when retrieving disk information.
- Ensured that errors are logged for better debugging and visibility.
- Updated checkDataLimit to await the checkAppDataDiskQuota function for proper asynchronous handling.

* fix comments

* fix: remove redundant appStorageQuota message from localization files

* lint

* fix: enhance disk space warning logic for USB disks

- Added a condition to warn users when free space on USB disks falls below 5% of total capacity.
- Improved the existing logic for displaying disk space warnings based on total disk size thresholds.

* update i18n

* Refactor data limit notification logic and update i18n messages for disk space warnings. Adjusted check intervals and improved toast notifications for low disk space alerts.

* Fix disk quota check logic in useDataLimit hook to correctly compare free space against 1GB threshold.

* refactor: update styles and improve navbar handling

- Removed unnecessary margin-bottom style from bubble markdown.
- Adjusted margin in Prompt component for better layout.
- Enhanced useAppInit hook to include navbar position logic for background styling.
- Added alignment to ErrorBlock alert for improved visual consistency.

* refactor: relocate checkDataLimit function to utils and update import in useAppInit hook

- Moved checkDataLimit function from useDataLimit hook to utils for better organization.
- Updated import path in useAppInit to reflect the new location of checkDataLimit.
- Removed the now obsolete useDataLimit hook file.

* refactor: update getDiskInfo API to specify return type

- Enhanced getDiskInfo function to explicitly define the return type as a Promise containing disk information or null.

* lint err

* fix: handle null response from getDiskInfo in checkAppDataDiskQuota

- Added a check for null response from getDiskInfo to prevent errors.
- Updated the logic to extract the free disk space only if diskInfo is valid.

---------

Co-authored-by: kangfenmao <kangfenmao@qq.com>
2025-09-11 21:04:20 +08:00

168 lines
5.2 KiB
TypeScript

import { loggerService } from '@logger'
import { isMac } from '@renderer/config/constant'
import { isLocalAi } from '@renderer/config/env'
import { useTheme } from '@renderer/context/ThemeProvider'
import db from '@renderer/databases'
import i18n from '@renderer/i18n'
import KnowledgeQueue from '@renderer/queue/KnowledgeQueue'
import MemoryService from '@renderer/services/MemoryService'
import { useAppDispatch } from '@renderer/store'
import { useAppSelector } from '@renderer/store'
import { handleSaveData } from '@renderer/store'
import { selectMemoryConfig } from '@renderer/store/memory'
import { setAvatar, setFilesPath, setResourcesPath, setUpdateState } from '@renderer/store/runtime'
import { delay, runAsyncFunction } from '@renderer/utils'
import { checkDataLimit } from '@renderer/utils'
import { defaultLanguage } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel'
import { useLiveQuery } from 'dexie-react-hooks'
import { useEffect } from 'react'
import { useDefaultModel } from './useAssistant'
import useFullScreenNotice from './useFullScreenNotice'
import { useRuntime } from './useRuntime'
import { useNavbarPosition, useSettings } from './useSettings'
import useUpdateHandler from './useUpdateHandler'
const logger = loggerService.withContext('useAppInit')
export function useAppInit() {
const dispatch = useAppDispatch()
const {
proxyUrl,
proxyBypassRules,
language,
windowStyle,
autoCheckUpdate,
proxyMode,
customCss,
enableDataCollection
} = useSettings()
const { isTopNavbar } = useNavbarPosition()
const { minappShow } = useRuntime()
const { setDefaultModel, setQuickModel, setTranslateModel } = useDefaultModel()
const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
const { theme } = useTheme()
const memoryConfig = useAppSelector(selectMemoryConfig)
useEffect(() => {
document.getElementById('spinner')?.remove()
// eslint-disable-next-line no-restricted-syntax
console.timeEnd('init')
// Initialize MemoryService after app is ready
MemoryService.getInstance()
}, [])
useEffect(() => {
window.api.getDataPathFromArgs().then((dataPath) => {
if (dataPath) {
window.navigate('/settings/data', { replace: true })
}
})
}, [])
useEffect(() => {
window.electron.ipcRenderer.on(IpcChannel.App_SaveData, async () => {
await handleSaveData()
})
}, [])
useUpdateHandler()
useFullScreenNotice()
useEffect(() => {
avatar?.value && dispatch(setAvatar(avatar.value))
}, [avatar, dispatch])
useEffect(() => {
runAsyncFunction(async () => {
const { isPackaged } = await window.api.getAppInfo()
if (isPackaged && autoCheckUpdate) {
await delay(2)
const { updateInfo } = await window.api.checkForUpdate()
dispatch(setUpdateState({ info: updateInfo }))
}
})
}, [dispatch, autoCheckUpdate])
useEffect(() => {
if (proxyMode === 'system') {
window.api.setProxy('system', undefined)
} else if (proxyMode === 'custom') {
proxyUrl && window.api.setProxy(proxyUrl, proxyBypassRules)
} else {
// set proxy to none for direct mode
window.api.setProxy('', undefined)
}
}, [proxyUrl, proxyMode, proxyBypassRules])
useEffect(() => {
i18n.changeLanguage(language || navigator.language || defaultLanguage)
}, [language])
useEffect(() => {
const transparentWindow = windowStyle === 'transparent' && isMac && !minappShow
if (minappShow && isTopNavbar) {
window.root.style.background =
windowStyle === 'transparent' && isMac ? 'var(--color-background)' : 'var(--navbar-background)'
return
}
window.root.style.background = transparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
}, [windowStyle, minappShow, theme, isTopNavbar])
useEffect(() => {
if (isLocalAi) {
const model = JSON.parse(import.meta.env.VITE_RENDERER_INTEGRATED_MODEL)
setDefaultModel(model)
setQuickModel(model)
setTranslateModel(model)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
// set files path
window.api.getAppInfo().then((info) => {
dispatch(setFilesPath(info.filesPath))
dispatch(setResourcesPath(info.resourcesPath))
})
}, [dispatch])
useEffect(() => {
KnowledgeQueue.checkAllBases()
}, [])
useEffect(() => {
let customCssElement = document.getElementById('user-defined-custom-css') as HTMLStyleElement
if (customCssElement) {
customCssElement.remove()
}
if (customCss) {
customCssElement = document.createElement('style')
customCssElement.id = 'user-defined-custom-css'
customCssElement.textContent = customCss
document.head.appendChild(customCssElement)
}
}, [customCss])
useEffect(() => {
// TODO: init data collection
}, [enableDataCollection])
// Update memory service configuration when it changes
useEffect(() => {
const memoryService = MemoryService.getInstance()
memoryService.updateConfig().catch((error) => {
logger.error('Failed to update memory config:', error)
})
}, [memoryConfig])
useEffect(() => {
checkDataLimit()
}, [])
}