Skip to content

Commit

Permalink
feat: add factory reset feature (janhq#1750)
Browse files Browse the repository at this point in the history
* feat(FactoryReset): add factory reset feature

Signed-off-by: nam <[email protected]>
Signed-off-by: James <[email protected]>
Co-authored-by: Faisal Amir <[email protected]>
Co-authored-by: James <[email protected]>
  • Loading branch information
3 people authored Jan 31, 2024
1 parent 5e58f67 commit 8151ef0
Show file tree
Hide file tree
Showing 18 changed files with 277 additions and 38 deletions.
2 changes: 1 addition & 1 deletion core/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* @description Enum of all the routes exposed by the app
*/
export enum AppRoute {
appDataPath = 'appDataPath',
openExternalUrl = 'openExternalUrl',
openAppDirectory = 'openAppDirectory',
openFileExplore = 'openFileExplorer',
Expand Down Expand Up @@ -62,6 +61,7 @@ export enum FileManagerRoute {
syncFile = 'syncFile',
getJanDataFolderPath = 'getJanDataFolderPath',
getResourcePath = 'getResourcePath',
getUserHomePath = 'getUserHomePath',
fileStat = 'fileStat',
writeBlob = 'writeBlob',
}
Expand Down
7 changes: 7 additions & 0 deletions core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ const openExternalUrl: (url: string) => Promise<any> = (url) =>
*/
const getResourcePath: () => Promise<string> = () => global.core.api?.getResourcePath()

/**
* Gets the user's home path.
* @returns return user's home path
*/
const getUserHomePath = (): Promise<string> => global.core.api?.getUserHomePath()

/**
* Log to file from browser processes.
*
Expand Down Expand Up @@ -127,5 +133,6 @@ export {
baseName,
log,
isSubdirectory,
getUserHomePath,
FileStat,
}
2 changes: 2 additions & 0 deletions core/src/node/api/routes/fileManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ export const fsRouter = async (app: HttpServer) => {

app.post(`/app/${FileManagerRoute.getResourcePath}`, async (request: any, reply: any) => {})

app.post(`/app/${FileManagerRoute.getUserHomePath}`, async (request: any, reply: any) => {})

app.post(`/app/${FileManagerRoute.fileStat}`, async (request: any, reply: any) => {})
}
6 changes: 5 additions & 1 deletion electron/handlers/fileManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ipcMain } from 'electron'
import { ipcMain, app } from 'electron'
// @ts-ignore
import reflect from '@alumna/reflect'

Expand Down Expand Up @@ -38,6 +38,10 @@ export function handleFileMangerIPCs() {
getResourcePath()
)

ipcMain.handle(FileManagerRoute.getUserHomePath, async (_event) =>
app.getPath('home')
)

// handle fs is directory here
ipcMain.handle(
FileManagerRoute.fileStat,
Expand Down
2 changes: 1 addition & 1 deletion electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"request": "^2.88.2",
"request-progress": "^3.0.0",
"rimraf": "^5.0.5",
"typescript": "^5.3.3",
"typescript": "^5.2.2",
"ulid": "^2.3.0",
"use-debounce": "^9.0.4"
},
Expand Down
2 changes: 1 addition & 1 deletion extensions/inference-nitro-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"rollup-plugin-sourcemaps": "^0.6.3",
"rollup-plugin-typescript2": "^0.36.0",
"run-script-os": "^1.1.6",
"typescript": "^5.3.3"
"typescript": "^5.2.2"
},
"dependencies": {
"@janhq/core": "file:../../core",
Expand Down
1 change: 1 addition & 0 deletions uikit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-context": "^1.0.1",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-icons": "^1.3.0",
Expand Down
8 changes: 6 additions & 2 deletions uikit/src/button/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
}

&-secondary-blue {
@apply bg-blue-200 text-blue-600 hover:bg-blue-500/50;
@apply bg-blue-200 text-blue-600 hover:bg-blue-300/50 dark:hover:bg-blue-200/80;
}

&-danger {
@apply bg-danger text-danger-foreground hover:bg-danger/90;
}

&-secondary-danger {
@apply bg-red-200 text-red-600 hover:bg-red-500/50;
@apply bg-red-200 text-red-600 hover:bg-red-300/50 dark:hover:bg-red-200/80;
}

&-outline {
Expand Down Expand Up @@ -67,14 +67,18 @@
[type='submit'] {
&.btn-primary {
@apply bg-primary hover:bg-primary/90;
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
}
&.btn-secondary {
@apply bg-secondary hover:bg-secondary/80;
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
}
&.btn-secondary-blue {
@apply bg-blue-200 text-blue-900 hover:bg-blue-200/80;
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
}
&.btn-danger {
@apply bg-danger hover:bg-danger/90;
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
}
}
29 changes: 29 additions & 0 deletions uikit/src/checkbox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'

import * as React from 'react'
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import { CheckIcon } from '@radix-ui/react-icons'

import { twMerge } from 'tailwind-merge'

const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={twMerge('checkbox', className)}
{...props}
>
<CheckboxPrimitive.Indicator
className={twMerge(
'flex flex-shrink-0 items-center justify-center text-current'
)}
>
<CheckIcon className="checkbox--icon" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName

export { Checkbox }
7 changes: 7 additions & 0 deletions uikit/src/checkbox/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.checkbox {
@apply border-border data-[state=checked]:bg-primary h-5 w-5 flex-shrink-0 rounded-md border data-[state=checked]:text-white;

&--icon {
@apply h-4 w-4;
}
}
1 change: 1 addition & 0 deletions uikit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './command'
export * from './textarea'
export * from './select'
export * from './slider'
export * from './checkbox'
1 change: 1 addition & 0 deletions uikit/src/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
@import './textarea/styles.scss';
@import './select/styles.scss';
@import './slider/styles.scss';
@import './checkbox/styles.scss';

.animate-spin {
animation: spin 1s linear infinite;
Expand Down
59 changes: 59 additions & 0 deletions web/hooks/useFactoryReset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useEffect, useState } from 'react'

import { fs, AppConfiguration, joinPath, getUserHomePath } from '@janhq/core'

export default function useFactoryReset() {
const [defaultJanDataFolder, setdefaultJanDataFolder] = useState('')

useEffect(() => {
async function getDefaultJanDataFolder() {
const homePath = await getUserHomePath()
const defaultJanDataFolder = await joinPath([homePath, 'jan'])
setdefaultJanDataFolder(defaultJanDataFolder)
}
getDefaultJanDataFolder()
}, [])

const resetAll = async (keepCurrentFolder?: boolean) => {
// read the place of jan data folder
const appConfiguration: AppConfiguration | undefined =
await window.core?.api?.getAppConfigurations()

if (!appConfiguration) {
console.debug('Failed to get app configuration')
}

console.debug('appConfiguration: ', appConfiguration)
const janDataFolderPath = appConfiguration!.data_folder

if (defaultJanDataFolder === janDataFolderPath) {
console.debug('Jan data folder is already at user home')
} else {
// if jan data folder is not at user home, we update the app configuration to point to user home
if (!keepCurrentFolder) {
const configuration: AppConfiguration = {
data_folder: defaultJanDataFolder,
}
await window.core?.api?.updateAppConfiguration(configuration)
}
}

const modelPath = await joinPath([janDataFolderPath, 'models'])
const threadPath = await joinPath([janDataFolderPath, 'threads'])

console.debug(`Removing models at ${modelPath}`)
await fs.rmdirSync(modelPath, { recursive: true })

console.debug(`Removing threads at ${threadPath}`)
await fs.rmdirSync(threadPath, { recursive: true })

// reset the localStorage
localStorage.clear()
await window.core?.api?.relaunch()
}

return {
defaultJanDataFolder,
resetAll,
}
}
7 changes: 4 additions & 3 deletions web/hooks/useSettings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'

import { fs, joinPath } from '@janhq/core'
import { atom, useAtom } from 'jotai'
Expand Down Expand Up @@ -32,7 +32,7 @@ export const useSettings = () => {
})
}

const readSettings = async () => {
const readSettings = useCallback(async () => {
if (!window?.core?.api) {
return
}
Expand All @@ -42,7 +42,8 @@ export const useSettings = () => {
return typeof settings === 'object' ? settings : JSON.parse(settings)
}
return {}
}
}, [])

const saveSettings = async ({
runMode,
notify,
Expand Down
6 changes: 1 addition & 5 deletions web/screens/LocalServer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,7 @@ const LocalServerScreen = () => {
}

useEffect(() => {
if (
localStorage.getItem(FIRST_TIME_VISIT_API_SERVER) === null ||
localStorage.getItem(FIRST_TIME_VISIT_API_SERVER) === 'true'
) {
localStorage.setItem(FIRST_TIME_VISIT_API_SERVER, 'true')
if (localStorage.getItem(FIRST_TIME_VISIT_API_SERVER) == null) {
setFirstTimeVisitAPIServer(true)
}
}, [firstTimeVisitAPIServer])
Expand Down
99 changes: 99 additions & 0 deletions web/screens/Settings/Advanced/FactoryReset/ModalConfirmReset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { useCallback, useEffect, useState } from 'react'

import { fs, AppConfiguration, joinPath, getUserHomePath } from '@janhq/core'

import {
Modal,
ModalPortal,
ModalContent,
ModalHeader,
ModalTitle,
ModalFooter,
ModalClose,
Button,
Checkbox,
Input,
} from '@janhq/uikit'
import { atom, useAtom } from 'jotai'

import useFactoryReset from '@/hooks/useFactoryReset'

export const modalValidationAtom = atom(false)

const ModalConfirmReset = () => {
const [modalValidation, setModalValidation] = useAtom(modalValidationAtom)
const { resetAll, defaultJanDataFolder } = useFactoryReset()
const [inputValue, setInputValue] = useState('')
const [currentDirectoryChecked, setCurrentDirectoryChecked] = useState(true)
const onFactoryResetClick = useCallback(
() => resetAll(currentDirectoryChecked),
[currentDirectoryChecked, resetAll]
)

return (
<Modal
open={modalValidation}
onOpenChange={() => setModalValidation(false)}
>
<ModalPortal />
<ModalContent>
<ModalHeader>
<ModalTitle>
Are you sure you want to reset to default settings?
</ModalTitle>
</ModalHeader>
<p className="text-muted-foreground">
It will reset the application to its original state, deleting all your
usage data, including model customizations and conversation history.
This action is irreversible.
</p>
<div>
<p className="mb-2 mt-1 text-muted-foreground">{`To confirm, please enter the word "RESET" below:`}</p>
<Input
placeholder='Enter "RESET"'
onChange={(e) => setInputValue(e.target.value)}
/>
</div>
<div className="flex flex-shrink-0 items-start space-x-2">
<Checkbox
id="currentDirectory"
checked={currentDirectoryChecked}
onCheckedChange={(e) => setCurrentDirectoryChecked(Boolean(e))}
/>
<div className="mt-0.5 flex flex-col">
<label
htmlFor="currentDirectory"
className="cursor-pointer text-sm font-medium leading-none"
>
Keep the current app data location
</label>
<p className="mt-2 leading-relaxed">
Otherwise it will reset back to its original location at:
{/* TODO should be from system */}
<span className="font-medium">{defaultJanDataFolder}</span>
</p>
</div>
</div>
<ModalFooter>
<div className="flex gap-x-2">
<ModalClose asChild onClick={() => setModalValidation(false)}>
<Button themes="outline">Cancel</Button>
</ModalClose>
<ModalClose asChild>
<Button
autoFocus
themes="danger"
disabled={inputValue !== 'RESET'}
onClick={onFactoryResetClick}
>
Reset Now
</Button>
</ModalClose>
</div>
</ModalFooter>
</ModalContent>
</Modal>
)
}

export default ModalConfirmReset
Loading

0 comments on commit 8151ef0

Please sign in to comment.