feat: update UI allow user change folder (#1738)
* feat: wip ui jan folder setting * change input disabled * finished change directory jan folder * fix overlap value input current path folder * make app reload to latest page * fix: add experimental feature toggle til the next release --------- Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
parent
1b794b5337
commit
6ba48bc1e3
@ -14,6 +14,7 @@ const buttonVariants = cva('btn', {
|
|||||||
outline: 'btn-outline',
|
outline: 'btn-outline',
|
||||||
secondary: 'btn-secondary',
|
secondary: 'btn-secondary',
|
||||||
secondaryBlue: 'btn-secondary-blue',
|
secondaryBlue: 'btn-secondary-blue',
|
||||||
|
secondaryDanger: 'btn-secondary-danger',
|
||||||
ghost: 'btn-ghost',
|
ghost: 'btn-ghost',
|
||||||
success: 'btn-success',
|
success: 'btn-success',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -9,13 +9,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-secondary-blue {
|
&-secondary-blue {
|
||||||
@apply bg-blue-200 text-blue-600 hover:bg-blue-500/80;
|
@apply bg-blue-200 text-blue-600 hover:bg-blue-500/50;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-danger {
|
&-danger {
|
||||||
@apply bg-danger text-danger-foreground hover:bg-danger/90;
|
@apply bg-danger text-danger-foreground hover:bg-danger/90;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-secondary-danger {
|
||||||
|
@apply bg-red-200 text-red-600 hover:bg-red-500/50;
|
||||||
|
}
|
||||||
|
|
||||||
&-outline {
|
&-outline {
|
||||||
@apply border-input border bg-transparent;
|
@apply border-input border bg-transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.input {
|
.input {
|
||||||
@apply border-border placeholder:text-muted-foreground flex h-9 w-full rounded-lg border bg-transparent px-3 py-1 transition-colors;
|
@apply border-border placeholder:text-muted-foreground flex h-9 w-full rounded-lg border bg-transparent px-3 py-1 transition-colors;
|
||||||
@apply disabled:cursor-not-allowed disabled:opacity-50;
|
@apply disabled:cursor-not-allowed disabled:bg-zinc-100;
|
||||||
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
|
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
|
||||||
@apply file:border-0 file:bg-transparent file:font-medium;
|
@apply file:border-0 file:bg-transparent file:font-medium;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,11 +9,14 @@ import RibbonNav from '@/containers/Layout/Ribbon'
|
|||||||
|
|
||||||
import TopBar from '@/containers/Layout/TopBar'
|
import TopBar from '@/containers/Layout/TopBar'
|
||||||
|
|
||||||
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
import { SUCCESS_SET_NEW_DESTINATION } from '@/hooks/useVaultDirectory'
|
||||||
|
|
||||||
const BaseLayout = (props: PropsWithChildren) => {
|
const BaseLayout = (props: PropsWithChildren) => {
|
||||||
const { children } = props
|
const { children } = props
|
||||||
const { mainViewState } = useMainViewState()
|
const { mainViewState, setMainViewState } = useMainViewState()
|
||||||
|
|
||||||
const { theme, setTheme } = useTheme()
|
const { theme, setTheme } = useTheme()
|
||||||
|
|
||||||
@ -21,6 +24,12 @@ const BaseLayout = (props: PropsWithChildren) => {
|
|||||||
setTheme(theme as string)
|
setTheme(theme as string)
|
||||||
}, [setTheme, theme])
|
}, [setTheme, theme])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (localStorage.getItem(SUCCESS_SET_NEW_DESTINATION) === 'true') {
|
||||||
|
setMainViewState(MainViewState.Settings)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-screen flex-1 overflow-hidden">
|
<div className="flex h-screen w-screen flex-1 overflow-hidden">
|
||||||
<RibbonNav />
|
<RibbonNav />
|
||||||
|
|||||||
@ -50,10 +50,12 @@ const availableShortcuts = [
|
|||||||
|
|
||||||
const ShortcutModal: React.FC = () => (
|
const ShortcutModal: React.FC = () => (
|
||||||
<Modal>
|
<Modal>
|
||||||
<ModalTrigger asChild>
|
<ModalTrigger>
|
||||||
<Button size="sm" themes="secondary">
|
<div>
|
||||||
Show
|
<Button size="sm" themes="secondaryBlue">
|
||||||
</Button>
|
Show
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</ModalTrigger>
|
</ModalTrigger>
|
||||||
<ModalContent className="max-w-2xl">
|
<ModalContent className="max-w-2xl">
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
|
|||||||
105
web/hooks/useVaultDirectory.ts
Normal file
105
web/hooks/useVaultDirectory.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
import { fs, AppConfiguration } from '@janhq/core'
|
||||||
|
|
||||||
|
import { atom, useAtom } from 'jotai'
|
||||||
|
|
||||||
|
import { useMainViewState } from './useMainViewState'
|
||||||
|
|
||||||
|
const isSameDirectoryAtom = atom(false)
|
||||||
|
const isDirectoryConfirmAtom = atom(false)
|
||||||
|
const isErrorSetNewDestAtom = atom(false)
|
||||||
|
const currentPathAtom = atom('')
|
||||||
|
const newDestinationPathAtom = atom('')
|
||||||
|
|
||||||
|
export const SUCCESS_SET_NEW_DESTINATION = 'successSetNewDestination'
|
||||||
|
|
||||||
|
export function useVaultDirectory() {
|
||||||
|
const [isSameDirectory, setIsSameDirectory] = useAtom(isSameDirectoryAtom)
|
||||||
|
const { setMainViewState } = useMainViewState()
|
||||||
|
const [isDirectoryConfirm, setIsDirectoryConfirm] = useAtom(
|
||||||
|
isDirectoryConfirmAtom
|
||||||
|
)
|
||||||
|
const [isErrorSetNewDest, setIsErrorSetNewDest] = useAtom(
|
||||||
|
isErrorSetNewDestAtom
|
||||||
|
)
|
||||||
|
const [currentPath, setCurrentPath] = useAtom(currentPathAtom)
|
||||||
|
const [newDestinationPath, setNewDestinationPath] = useAtom(
|
||||||
|
newDestinationPathAtom
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.core?.api
|
||||||
|
?.getAppConfigurations()
|
||||||
|
?.then((appConfig: AppConfiguration) => {
|
||||||
|
setCurrentPath(appConfig.data_folder)
|
||||||
|
})
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const setNewDestination = async () => {
|
||||||
|
const destFolder = await window.core?.api?.selectDirectory()
|
||||||
|
setNewDestinationPath(destFolder)
|
||||||
|
|
||||||
|
if (destFolder) {
|
||||||
|
console.debug(`Destination folder selected: ${destFolder}`)
|
||||||
|
try {
|
||||||
|
const appConfiguration: AppConfiguration =
|
||||||
|
await window.core?.api?.getAppConfigurations()
|
||||||
|
const currentJanDataFolder = appConfiguration.data_folder
|
||||||
|
|
||||||
|
if (currentJanDataFolder === destFolder) {
|
||||||
|
console.debug(
|
||||||
|
`Destination folder is the same as current folder. Ignore..`
|
||||||
|
)
|
||||||
|
setIsSameDirectory(true)
|
||||||
|
setIsDirectoryConfirm(false)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
setIsSameDirectory(false)
|
||||||
|
setIsDirectoryConfirm(true)
|
||||||
|
}
|
||||||
|
setIsErrorSetNewDest(false)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error: ${e}`)
|
||||||
|
setIsErrorSetNewDest(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyNewDestination = async () => {
|
||||||
|
try {
|
||||||
|
const appConfiguration: AppConfiguration =
|
||||||
|
await window.core?.api?.getAppConfigurations()
|
||||||
|
const currentJanDataFolder = appConfiguration.data_folder
|
||||||
|
|
||||||
|
appConfiguration.data_folder = newDestinationPath
|
||||||
|
|
||||||
|
await fs.syncFile(currentJanDataFolder, newDestinationPath)
|
||||||
|
await window.core?.api?.updateAppConfiguration(appConfiguration)
|
||||||
|
console.debug(
|
||||||
|
`File sync finished from ${currentPath} to ${newDestinationPath}`
|
||||||
|
)
|
||||||
|
|
||||||
|
setIsErrorSetNewDest(false)
|
||||||
|
localStorage.setItem(SUCCESS_SET_NEW_DESTINATION, 'true')
|
||||||
|
await window.core?.api?.relaunch()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error: ${e}`)
|
||||||
|
setIsErrorSetNewDest(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setNewDestination,
|
||||||
|
newDestinationPath,
|
||||||
|
applyNewDestination,
|
||||||
|
isSameDirectory,
|
||||||
|
setIsDirectoryConfirm,
|
||||||
|
isDirectoryConfirm,
|
||||||
|
setIsSameDirectory,
|
||||||
|
currentPath,
|
||||||
|
isErrorSetNewDest,
|
||||||
|
setIsErrorSetNewDest,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalPortal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalTitle,
|
||||||
|
ModalFooter,
|
||||||
|
ModalClose,
|
||||||
|
Button,
|
||||||
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useVaultDirectory } from '@/hooks/useVaultDirectory'
|
||||||
|
|
||||||
|
const ModalChangeDirectory = () => {
|
||||||
|
const {
|
||||||
|
isDirectoryConfirm,
|
||||||
|
setIsDirectoryConfirm,
|
||||||
|
applyNewDestination,
|
||||||
|
newDestinationPath,
|
||||||
|
} = useVaultDirectory()
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={isDirectoryConfirm}
|
||||||
|
onOpenChange={() => setIsDirectoryConfirm(false)}
|
||||||
|
>
|
||||||
|
<ModalPortal />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalTitle>Relocate Jan Data Folder</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Are you sure you want to relocate Jan data folder to{' '}
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{newDestinationPath}
|
||||||
|
</span>
|
||||||
|
? A restart will be required afterward.
|
||||||
|
</p>
|
||||||
|
<ModalFooter>
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<ModalClose asChild onClick={() => setIsDirectoryConfirm(false)}>
|
||||||
|
<Button themes="ghost">Cancel</Button>
|
||||||
|
</ModalClose>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button onClick={applyNewDestination} autoFocus>
|
||||||
|
Yes, Proceed
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalChangeDirectory
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalPortal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalTitle,
|
||||||
|
ModalFooter,
|
||||||
|
ModalClose,
|
||||||
|
Button,
|
||||||
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useVaultDirectory } from '@/hooks/useVaultDirectory'
|
||||||
|
|
||||||
|
const ModalErrorSetDestGlobal = () => {
|
||||||
|
const { isErrorSetNewDest, setIsErrorSetNewDest } = useVaultDirectory()
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={isErrorSetNewDest}
|
||||||
|
onOpenChange={() => setIsErrorSetNewDest(false)}
|
||||||
|
>
|
||||||
|
<ModalPortal />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalTitle>Error Occurred</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Oops! Something went wrong. Jan data folder remains the same. Please
|
||||||
|
try again.
|
||||||
|
</p>
|
||||||
|
<ModalFooter>
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<ModalClose asChild onClick={() => setIsErrorSetNewDest(false)}>
|
||||||
|
<Button themes="danger">Got it</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalErrorSetDestGlobal
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalPortal,
|
||||||
|
ModalContent,
|
||||||
|
ModalHeader,
|
||||||
|
ModalTitle,
|
||||||
|
ModalFooter,
|
||||||
|
ModalClose,
|
||||||
|
Button,
|
||||||
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
|
import { useVaultDirectory } from '@/hooks/useVaultDirectory'
|
||||||
|
|
||||||
|
const ModalSameDirectory = () => {
|
||||||
|
const { isSameDirectory, setIsSameDirectory, setNewDestination } =
|
||||||
|
useVaultDirectory()
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={isSameDirectory}
|
||||||
|
onOpenChange={() => setIsSameDirectory(false)}
|
||||||
|
>
|
||||||
|
<ModalPortal />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
<ModalTitle>Unable to move files</ModalTitle>
|
||||||
|
</ModalHeader>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{`It seems like the folder you've chosen same with current directory`}
|
||||||
|
</p>
|
||||||
|
<ModalFooter>
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<ModalClose asChild onClick={() => setIsSameDirectory(false)}>
|
||||||
|
<Button themes="ghost">Cancel</Button>
|
||||||
|
</ModalClose>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button themes="danger" onClick={setNewDestination} autoFocus>
|
||||||
|
Choose a different folder
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalSameDirectory
|
||||||
52
web/screens/Settings/Advanced/DataFolder/index.tsx
Normal file
52
web/screens/Settings/Advanced/DataFolder/index.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Button, Input } from '@janhq/uikit'
|
||||||
|
import { PencilIcon, FolderOpenIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import { useVaultDirectory } from '@/hooks/useVaultDirectory'
|
||||||
|
|
||||||
|
import ModalChangeDirectory from './ModalChangeDirectory'
|
||||||
|
import ModalErrorSetDestGlobal from './ModalErrorSetDestGlobal'
|
||||||
|
import ModalSameDirectory from './ModalSameDirectory'
|
||||||
|
|
||||||
|
const DataFolder = () => {
|
||||||
|
const { currentPath, setNewDestination } = useVaultDirectory()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<h6 className="text-sm font-semibold capitalize">
|
||||||
|
Jan Data Folder
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
Where messages, model configurations, and other user data are
|
||||||
|
placed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-x-3">
|
||||||
|
<div className="relative">
|
||||||
|
<Input value={currentPath} className="w-[240px] pr-8" disabled />
|
||||||
|
<FolderOpenIcon
|
||||||
|
size={16}
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
themes="outline"
|
||||||
|
className="h-9 w-9 p-0"
|
||||||
|
onClick={setNewDestination}
|
||||||
|
>
|
||||||
|
<PencilIcon size={16} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ModalSameDirectory />
|
||||||
|
<ModalChangeDirectory />
|
||||||
|
<ModalErrorSetDestGlobal />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DataFolder
|
||||||
@ -9,7 +9,7 @@ import {
|
|||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
|
||||||
import { fs, AppConfiguration } from '@janhq/core'
|
import { fs } from '@janhq/core'
|
||||||
import { Switch, Button, Input } from '@janhq/uikit'
|
import { Switch, Button, Input } from '@janhq/uikit'
|
||||||
|
|
||||||
import ShortcutModal from '@/containers/ShortcutModal'
|
import ShortcutModal from '@/containers/ShortcutModal'
|
||||||
@ -20,6 +20,8 @@ import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|||||||
|
|
||||||
import { useSettings } from '@/hooks/useSettings'
|
import { useSettings } from '@/hooks/useSettings'
|
||||||
|
|
||||||
|
import DataFolder from './DataFolder'
|
||||||
|
|
||||||
const Advanced = () => {
|
const Advanced = () => {
|
||||||
const {
|
const {
|
||||||
experimentalFeature,
|
experimentalFeature,
|
||||||
@ -31,6 +33,7 @@ const Advanced = () => {
|
|||||||
} = useContext(FeatureToggleContext)
|
} = useContext(FeatureToggleContext)
|
||||||
const [partialProxy, setPartialProxy] = useState<string>(proxy)
|
const [partialProxy, setPartialProxy] = useState<string>(proxy)
|
||||||
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
|
const [gpuEnabled, setGpuEnabled] = useState<boolean>(false)
|
||||||
|
|
||||||
const { readSettings, saveSettings, validateSettings, setShowNotification } =
|
const { readSettings, saveSettings, validateSettings, setShowNotification } =
|
||||||
useSettings()
|
useSettings()
|
||||||
const onProxyChange = useCallback(
|
const onProxyChange = useCallback(
|
||||||
@ -46,17 +49,6 @@ const Advanced = () => {
|
|||||||
[setPartialProxy, setProxy]
|
[setPartialProxy, setProxy]
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: remove me later.
|
|
||||||
const [currentPath, setCurrentPath] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.core?.api
|
|
||||||
?.getAppConfigurations()
|
|
||||||
?.then((appConfig: AppConfiguration) => {
|
|
||||||
setCurrentPath(appConfig.data_folder)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
readSettings().then((settings) => {
|
readSettings().then((settings) => {
|
||||||
setGpuEnabled(settings.run_mode === 'gpu')
|
setGpuEnabled(settings.run_mode === 'gpu')
|
||||||
@ -73,45 +65,55 @@ const Advanced = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const onJanVaultDirectoryClick = async () => {
|
|
||||||
const destFolder = await window.core?.api?.selectDirectory()
|
|
||||||
if (destFolder) {
|
|
||||||
console.debug(`Destination folder selected: ${destFolder}`)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const appConfiguration: AppConfiguration =
|
|
||||||
await window.core?.api?.getAppConfigurations()
|
|
||||||
const currentJanDataFolder = appConfiguration.data_folder
|
|
||||||
if (currentJanDataFolder === destFolder) {
|
|
||||||
console.debug(
|
|
||||||
`Destination folder is the same as current folder. Ignore..`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
appConfiguration.data_folder = destFolder
|
|
||||||
|
|
||||||
await fs.syncFile(currentJanDataFolder, destFolder)
|
|
||||||
await window.core?.api?.updateAppConfiguration(appConfiguration)
|
|
||||||
console.debug(
|
|
||||||
`File sync finished from ${currentJanDataFolder} to ${destFolder}`
|
|
||||||
)
|
|
||||||
await window.core?.api?.relaunch()
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Error: ${e}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="block w-full">
|
<div className="block w-full">
|
||||||
|
{/* Keyboard shortcut */}
|
||||||
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<h6 className="text-sm font-semibold capitalize">
|
||||||
|
Keyboard Shortcuts
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
Shortcuts that you might find useful in Jan app.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ShortcutModal />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Experimental */}
|
||||||
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
|
<div className="flex gap-x-2">
|
||||||
|
<h6 className="text-sm font-semibold capitalize">
|
||||||
|
Experimental Mode
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
Enable experimental features that may be unstable tested.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={experimentalFeature}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
if (e === true) {
|
||||||
|
setExperimentalFeature(true)
|
||||||
|
} else {
|
||||||
|
setExperimentalFeature(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* CPU / GPU switching */}
|
{/* CPU / GPU switching */}
|
||||||
{!isMac && (
|
{!isMac && (
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<h6 className="text-sm font-semibold capitalize">NVidia GPU</h6>
|
<h6 className="text-sm font-semibold capitalize">NVidia GPU</h6>
|
||||||
</div>
|
</div>
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
<p className="leading-relaxed">
|
||||||
Enable GPU acceleration for NVidia GPUs.
|
Enable GPU acceleration for NVidia GPUs.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -133,36 +135,17 @@ const Advanced = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Experimental */}
|
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
{/* Directory */}
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
{experimentalFeature && <DataFolder />}
|
||||||
<div className="flex gap-x-2">
|
|
||||||
<h6 className="text-sm font-semibold capitalize">
|
|
||||||
Experimental Mode
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
|
||||||
Enable experimental features that may be unstable tested.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={experimentalFeature}
|
|
||||||
onCheckedChange={(e) => {
|
|
||||||
if (e === true) {
|
|
||||||
setExperimentalFeature(true)
|
|
||||||
} else {
|
|
||||||
setExperimentalFeature(false)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* Proxy */}
|
{/* Proxy */}
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<h6 className="text-sm font-semibold capitalize">HTTPS Proxy</h6>
|
<h6 className="text-sm font-semibold capitalize">HTTPS Proxy</h6>
|
||||||
</div>
|
</div>
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
<p className="leading-relaxed">
|
||||||
Specify the HTTPS proxy or leave blank (proxy auto-configuration and
|
Specify the HTTPS proxy or leave blank (proxy auto-configuration and
|
||||||
SOCKS not supported).
|
SOCKS not supported).
|
||||||
</p>
|
</p>
|
||||||
@ -173,15 +156,16 @@ const Advanced = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Ignore SSL certificates */}
|
{/* Ignore SSL certificates */}
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<h6 className="text-sm font-semibold capitalize">
|
<h6 className="text-sm font-semibold capitalize">
|
||||||
Ignore SSL certificates
|
Ignore SSL certificates
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
<p className="leading-relaxed">
|
||||||
Allow self-signed or unverified certificates - may be required for
|
Allow self-signed or unverified certificates - may be required for
|
||||||
certain proxies.
|
certain proxies.
|
||||||
</p>
|
</p>
|
||||||
@ -197,79 +181,19 @@ const Advanced = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{window.electronAPI && (
|
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
{/* Claer log */}
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
|
||||||
<div className="flex gap-x-2">
|
|
||||||
<h6 className="text-sm font-semibold capitalize">
|
|
||||||
Open App Directory
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
|
||||||
Open the directory where your app data, like conversation history
|
|
||||||
and model configurations, is located.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
themes="secondary"
|
|
||||||
onClick={() => window.electronAPI.openAppDirectory()}
|
|
||||||
>
|
|
||||||
Open
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
<div className="flex-shrink-0 space-y-1.5">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<h6 className="text-sm font-semibold capitalize">Clear logs</h6>
|
<h6 className="text-sm font-semibold capitalize">Clear logs</h6>
|
||||||
</div>
|
</div>
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
<p className="leading-relaxed">Clear all logs from Jan app.</p>
|
||||||
Clear all logs from Jan app.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Button size="sm" themes="secondary" onClick={clearLogs}>
|
<Button size="sm" themes="secondaryDanger" onClick={clearLogs}>
|
||||||
Clear
|
Clear
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{experimentalFeature && (
|
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
|
||||||
<div className="flex gap-x-2">
|
|
||||||
<h6 className="text-sm font-semibold capitalize">
|
|
||||||
Jan Data Folder
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
|
||||||
Where messages, model configurations, and other user data is
|
|
||||||
placed.
|
|
||||||
</p>
|
|
||||||
<p className="whitespace-pre-wrap leading-relaxed text-gray-500">
|
|
||||||
{`${currentPath}`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
themes="secondary"
|
|
||||||
onClick={onJanVaultDirectoryClick}
|
|
||||||
>
|
|
||||||
Select
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
|
||||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
|
||||||
<div className="flex gap-x-2">
|
|
||||||
<h6 className="text-sm font-semibold capitalize">
|
|
||||||
Keyboard Shortcuts
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
<p className="whitespace-pre-wrap leading-relaxed">
|
|
||||||
Shortcuts that you might find useful in Jan app.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ShortcutModal />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,7 +111,7 @@ const ExtensionCatalog = () => {
|
|||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
themes="secondary"
|
themes="secondaryBlue"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -7,14 +7,14 @@ import { motion as m } from 'framer-motion'
|
|||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import { SUCCESS_SET_NEW_DESTINATION } from '@/hooks/useVaultDirectory'
|
||||||
|
|
||||||
import Advanced from '@/screens/Settings/Advanced'
|
import Advanced from '@/screens/Settings/Advanced'
|
||||||
import AppearanceOptions from '@/screens/Settings/Appearance'
|
import AppearanceOptions from '@/screens/Settings/Appearance'
|
||||||
import ExtensionCatalog from '@/screens/Settings/CoreExtensions'
|
import ExtensionCatalog from '@/screens/Settings/CoreExtensions'
|
||||||
|
|
||||||
import Models from '@/screens/Settings/Models'
|
import Models from '@/screens/Settings/Models'
|
||||||
|
|
||||||
import { formatExtensionsName } from '@/utils/converter'
|
|
||||||
|
|
||||||
const SettingsScreen = () => {
|
const SettingsScreen = () => {
|
||||||
const [activeStaticMenu, setActiveStaticMenu] = useState('My Models')
|
const [activeStaticMenu, setActiveStaticMenu] = useState('My Models')
|
||||||
const [menus, setMenus] = useState<any[]>([])
|
const [menus, setMenus] = useState<any[]>([])
|
||||||
@ -46,6 +46,12 @@ const SettingsScreen = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (localStorage.getItem(SUCCESS_SET_NEW_DESTINATION) === 'true') {
|
||||||
|
setActiveStaticMenu('Advanced Settings')
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full bg-background">
|
<div className="flex h-full bg-background">
|
||||||
<div className="flex h-full w-64 flex-shrink-0 flex-col overflow-y-auto border-r border-border">
|
<div className="flex h-full w-64 flex-shrink-0 flex-col overflow-y-auto border-r border-border">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user