import { createFileRoute } from '@tanstack/react-router' import { route } from '@/constants/routes' import SettingsMenu from '@/containers/SettingsMenu' import HeaderPage from '@/containers/HeaderPage' import { Switch } from '@/components/ui/switch' import { Button } from '@/components/ui/button' import { Card, CardItem } from '@/containers/Card' import { useTranslation } from '@/i18n/react-i18next-compat' import { useGeneralSetting } from '@/hooks/useGeneralSetting' import { useAppUpdater } from '@/hooks/useAppUpdater' import { useEffect, useState, useCallback } from 'react' import ChangeDataFolderLocation from '@/containers/dialogs/ChangeDataFolderLocation' import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog' import { useServiceHub } from '@/hooks/useServiceHub' import { IconBrandDiscord, IconBrandGithub, IconExternalLink, IconFolder, IconLogs, IconCopy, IconCopyCheck, } from '@tabler/icons-react' // import { windowKey } from '@/constants/windows' import { toast } from 'sonner' import { isDev } from '@/lib/utils' import { SystemEvent } from '@/types/events' import { Input } from '@/components/ui/input' import { useHardware } from '@/hooks/useHardware' import LanguageSwitcher from '@/containers/LanguageSwitcher' import { PlatformFeatures } from '@/lib/platform/const' import { PlatformFeature } from '@/lib/platform/types' // eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.settings.general as any)({ component: General, }) function General() { const { t } = useTranslation() const { spellCheckChatInput, setSpellCheckChatInput, huggingfaceToken, setHuggingfaceToken, } = useGeneralSetting() const serviceHub = useServiceHub() const openFileTitle = (): string => { if (IS_MACOS) { return t('settings:general.showInFinder') } else if (IS_WINDOWS) { return t('settings:general.showInFileExplorer') } else { return t('settings:general.openContainingFolder') } } const { checkForUpdate } = useAppUpdater() const { pausePolling } = useHardware() const [janDataFolder, setJanDataFolder] = useState() const [isCopied, setIsCopied] = useState(false) const [selectedNewPath, setSelectedNewPath] = useState(null) const [isDialogOpen, setIsDialogOpen] = useState(false) const [isCheckingUpdate, setIsCheckingUpdate] = useState(false) useEffect(() => { const fetchDataFolder = async () => { const path = await serviceHub.app().getJanDataFolder() setJanDataFolder(path) } fetchDataFolder() }, [serviceHub]) const resetApp = async () => { pausePolling() // TODO: Loading indicator await serviceHub.app().factoryReset() } const handleOpenLogs = async () => { try { await serviceHub.window().openLogsWindow() } catch (error) { console.error('Failed to open logs window:', error) } } const copyToClipboard = async (text: string) => { try { await navigator.clipboard.writeText(text) setIsCopied(true) setTimeout(() => setIsCopied(false), 2000) // Reset after 2 seconds } catch (error) { console.error('Failed to copy to clipboard:', error) } } const handleDataFolderChange = async () => { const selectedPath = await serviceHub.dialog().open({ multiple: false, directory: true, defaultPath: janDataFolder, }) if (selectedPath === janDataFolder) return if (selectedPath !== null) { setSelectedNewPath(selectedPath as string) setIsDialogOpen(true) } } const confirmDataFolderChange = async () => { if (selectedNewPath) { try { await serviceHub.models().stopAllModels() serviceHub.events().emit(SystemEvent.KILL_SIDECAR) setTimeout(async () => { try { await serviceHub.app().relocateJanDataFolder(selectedNewPath) setJanDataFolder(selectedNewPath) // Only relaunch if relocation was successful window.core?.api?.relaunch() setSelectedNewPath(null) setIsDialogOpen(false) } catch (error) { console.error(error) toast.error( error instanceof Error ? error.message : t('settings:general.failedToRelocateDataFolder') ) } }, 1000) } catch (error) { console.error('Failed to relocate data folder:', error) // Revert the data folder path on error const originalPath = await serviceHub.app().getJanDataFolder() setJanDataFolder(originalPath) toast.error(t('settings:general.failedToRelocateDataFolderDesc')) } } } const handleCheckForUpdate = useCallback(async () => { setIsCheckingUpdate(true) try { if (isDev()) return toast.info(t('settings:general.devVersion')) const update = await checkForUpdate(true) if (!update) { toast.info(t('settings:general.noUpdateAvailable')) } // If update is available, the AppUpdater dialog will automatically show } catch (error) { console.error('Failed to check for updates:', error) toast.error(t('settings:general.updateError')) } finally { setIsCheckingUpdate(false) } }, [t, checkForUpdate]) return (

{t('common:settings')}

{/* General */} {PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && ( v{VERSION} } /> )} {!AUTO_UPDATER_DISABLED && PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && (
{isCheckingUpdate ? t('settings:general.checkingForUpdates') : t('settings:general.checkForUpdates')}
} /> )} } />
{/* Data folder - Desktop only */} {PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && ( {t('settings:dataFolder.appDataDesc', { ns: 'settings', })}  
{janDataFolder}
} actions={ <> {selectedNewPath && ( { setIsDialogOpen(open) if (!open) { setSelectedNewPath(null) } }} >
)} } />
} />
)} {/* Advanced - Desktop only */} {PlatformFeatures[PlatformFeature.SYSTEM_INTEGRATIONS] && ( {t('settings:general.factoryResetTitle')} {t('settings:general.factoryResetDesc')} } /> )} {/* Other */} setSpellCheckChatInput(e)} /> } /> {PlatformFeatures[PlatformFeature.MODEL_HUB] && ( setHuggingfaceToken(e.target.value)} placeholder={'hf_xxx'} required /> } /> )} {/* Resources */}
{t('settings:general.viewDocs')}
} />
{t('settings:general.viewReleases')}
} />
{/* Community */}
} />
} />
{/* Support */}
{t('settings:general.reportIssue')}
} />
{/* Credits */}

{t('settings:general.creditsDesc1')}

{t('settings:general.creditsDesc2')}

} />
) }