From 6676e0ced8061f88a72203300541834017890480 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 21 May 2025 10:48:10 +0700 Subject: [PATCH] chore: add relocate jan data folder function to new FE (#5043) * chore: typo * fix: linter issues * chore: fix linter * chore: fix linter * chore: add relocate data folder --- .../browser/extensions/hardwareManagement.ts | 2 +- .../src/index.ts | 2 +- package.json | 2 +- src-tauri/src/core/cmd.rs | 61 ++++++++++++++++++- src-tauri/src/lib.rs | 1 + web-app/src/containers/LanguageSwitcher.tsx | 1 - web-app/src/containers/ModelSetting.tsx | 2 - .../DropdownControl.tsx | 4 +- .../dynamicControllerSetting/index.tsx | 4 +- web-app/src/hooks/useGeneralSetting.ts | 2 - web-app/src/hooks/useHardware.ts | 2 +- web-app/src/lib/completion.ts | 4 +- web-app/src/lib/service.ts | 1 + web-app/src/routes/settings/general.tsx | 8 ++- web-app/src/services/app.ts | 9 +++ web-app/src/services/hardware.ts | 2 +- web-app/src/services/mcp.ts | 4 +- web-app/src/services/providers.ts | 2 +- web-app/src/types/app.d.ts | 1 + web-app/src/types/global.d.ts | 23 ++++--- web-app/src/types/modelProviders.d.ts | 6 +- 21 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 web-app/src/types/app.d.ts diff --git a/core/src/browser/extensions/hardwareManagement.ts b/core/src/browser/extensions/hardwareManagement.ts index 1f7c36287..5de3c9257 100644 --- a/core/src/browser/extensions/hardwareManagement.ts +++ b/core/src/browser/extensions/hardwareManagement.ts @@ -19,7 +19,7 @@ export abstract class HardwareManagementExtension extends BaseExtension { /** * @returns A Promise that resolves to an object of set active gpus. */ - abstract setAvtiveGpu(data: { gpus: number[] }): Promise<{ + abstract setActiveGpu(data: { gpus: number[] }): Promise<{ message: string activated_gpus: number[] }> diff --git a/extensions/hardware-management-extension/src/index.ts b/extensions/hardware-management-extension/src/index.ts index 665dd05ef..bd94f3828 100644 --- a/extensions/hardware-management-extension/src/index.ts +++ b/extensions/hardware-management-extension/src/index.ts @@ -51,7 +51,7 @@ export default class JSONHardwareManagementExtension extends HardwareManagementE /** * @returns A Promise that resolves to an object of set gpu activate. */ - async setAvtiveGpu(data: { gpus: number[] }): Promise<{ + async setActiveGpu(data: { gpus: number[] }): Promise<{ message: string activated_gpus: number[] }> { diff --git a/package.json b/package.json index 53de04f95..79c64dfa8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ] }, "scripts": { - "lint": "yarn workspace jan lint && yarn workspace @janhq/web lint", + "lint": "yarn workspace @janhq/web-app lint", "test:unit": "jest", "test:coverage": "yarn workspace @janhq/web-app test", "test": "yarn workspace @janhq/web-app test", diff --git a/src-tauri/src/core/cmd.rs b/src-tauri/src/core/cmd.rs index 3e816befe..296166d2c 100644 --- a/src-tauri/src/core/cmd.rs +++ b/src-tauri/src/core/cmd.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{fs, path::PathBuf}; +use std::{fs, path::PathBuf, io}; use tauri::{AppHandle, Manager, Runtime, State}; use super::{server, setup, state::AppState}; @@ -267,6 +267,65 @@ pub fn get_user_home_path(app: AppHandle) -> String { return get_app_configurations(app.clone()).data_folder; } +/// Recursively copy a directory from src to dst +fn copy_dir_recursive(src: &PathBuf, dst: &PathBuf) -> Result<(), io::Error> { + if !dst.exists() { + fs::create_dir_all(dst)?; + } + + for entry in fs::read_dir(src)? { + let entry = entry?; + let file_type = entry.file_type()?; + let src_path = entry.path(); + let dst_path = dst.join(entry.file_name()); + + if file_type.is_dir() { + copy_dir_recursive(&src_path, &dst_path)?; + } else { + fs::copy(&src_path, &dst_path)?; + } + } + + Ok(()) +} + +#[tauri::command] +pub fn change_app_data_folder( + app_handle: tauri::AppHandle, + new_data_folder: String, +) -> Result<(), String> { + // Get current data folder path + let current_data_folder = get_jan_data_folder_path(app_handle.clone()); + let new_data_folder_path = PathBuf::from(&new_data_folder); + + // Create the new data folder if it doesn't exist + if !new_data_folder_path.exists() { + fs::create_dir_all(&new_data_folder_path) + .map_err(|e| format!("Failed to create new data folder: {}", e))?; + } + + // Copy all files from the old folder to the new one + if current_data_folder.exists() { + log::info!( + "Copying data from {:?} to {:?}", + current_data_folder, + new_data_folder_path + ); + + copy_dir_recursive(¤t_data_folder, &new_data_folder_path) + .map_err(|e| format!("Failed to copy data to new folder: {}", e))?; + } else { + log::info!("Current data folder does not exist, nothing to copy"); + } + + // Update the configuration to point to the new folder + let mut configuration = get_app_configurations(app_handle.clone()); + configuration.data_folder = new_data_folder; + + // Save the updated configuration + update_app_configuration(app_handle, configuration) +} + #[tauri::command] pub fn app_token(state: State<'_, AppState>) -> Option { state.app_token.clone() diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 9a3909e62..b89918e88 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -46,6 +46,7 @@ pub fn run() { core::cmd::start_server, core::cmd::stop_server, core::cmd::read_logs, + core::cmd::change_app_data_folder, // MCP commands core::mcp::get_tools, core::mcp::call_tool, diff --git a/web-app/src/containers/LanguageSwitcher.tsx b/web-app/src/containers/LanguageSwitcher.tsx index aa05af6f9..eeeafc1bb 100644 --- a/web-app/src/containers/LanguageSwitcher.tsx +++ b/web-app/src/containers/LanguageSwitcher.tsx @@ -20,7 +20,6 @@ export default function LanguageSwitcher() { const changeLanguage = (lng: string) => { i18n.changeLanguage(lng) - // @ts-ignore setCurrentLanguage(lng as Language) } diff --git a/web-app/src/containers/ModelSetting.tsx b/web-app/src/containers/ModelSetting.tsx index fe573cc79..cfcd6f9c2 100644 --- a/web-app/src/containers/ModelSetting.tsx +++ b/web-app/src/containers/ModelSetting.tsx @@ -41,7 +41,6 @@ export function ModelSetting({ model, provider }: ModelSettingProps) { [key]: { ...(model.settings?.[key] != null ? model.settings?.[key] : {}), controller_props: { - // @ts-ignore ...(model.settings?.[key]?.controller_props ?? {}), value: value, }, @@ -67,7 +66,6 @@ export function ModelSetting({ model, provider }: ModelSettingProps) { updateModel({ id: model.id, settings: Object.entries(updatedModel.settings).map(([key, value]) => ({ - // @ts-ignore [key]: value.controller_props?.value, })) as ModelSettingParams, }) diff --git a/web-app/src/containers/dynamicControllerSetting/DropdownControl.tsx b/web-app/src/containers/dynamicControllerSetting/DropdownControl.tsx index d611df094..4328aca60 100644 --- a/web-app/src/containers/dynamicControllerSetting/DropdownControl.tsx +++ b/web-app/src/containers/dynamicControllerSetting/DropdownControl.tsx @@ -8,8 +8,8 @@ import { // Dropdown component type DropdownControlProps = { value: string - options?: Array<{ value: string; name: string }> - onChange: (value: string) => void + options?: Array<{ value: number | string; name: string }> + onChange: (value: number | string) => void } export function DropdownControl({ diff --git a/web-app/src/containers/dynamicControllerSetting/index.tsx b/web-app/src/containers/dynamicControllerSetting/index.tsx index a2c25f3b1..d0c7ee30a 100644 --- a/web-app/src/containers/dynamicControllerSetting/index.tsx +++ b/web-app/src/containers/dynamicControllerSetting/index.tsx @@ -10,12 +10,12 @@ type DynamicControllerProps = { title?: string className?: string description?: string - controllerType: 'input' | 'checkbox' | 'dropdown' | 'textarea' | 'slider' + controllerType: 'input' | 'checkbox' | 'dropdown' | 'textarea' | 'slider' | string controllerProps: { value?: string | boolean | number placeholder?: string type?: string - options?: Array<{ value: string; name: string }> + options?: Array<{ value: number | string; name: string }> input_actions?: string[] rows?: number min?: number diff --git a/web-app/src/hooks/useGeneralSetting.ts b/web-app/src/hooks/useGeneralSetting.ts index 9ed0abaa5..5335f245c 100644 --- a/web-app/src/hooks/useGeneralSetting.ts +++ b/web-app/src/hooks/useGeneralSetting.ts @@ -3,11 +3,9 @@ import { persist, createJSONStorage } from 'zustand/middleware' import { localStorageKey } from '@/constants/localStorage' type LeftPanelStoreState = { - // @ts-ignore currentLanguage: Language spellCheckChatInput: boolean setSpellCheckChatInput: (value: boolean) => void - // @ts-ignore setCurrentLanguage: (value: Language) => void } diff --git a/web-app/src/hooks/useHardware.ts b/web-app/src/hooks/useHardware.ts index bac87f80b..7ad067cc8 100644 --- a/web-app/src/hooks/useHardware.ts +++ b/web-app/src/hooks/useHardware.ts @@ -182,7 +182,7 @@ export const useHardware = create()( } } setActiveGpus({ - gpus: newGPUs.filter((e) => e.activated).map((e) => e.id as unknown as number), + gpus: newGPUs.filter((e) => e.activated).map((e) => parseInt(e.id)), }) return { hardwareData: { diff --git a/web-app/src/lib/completion.ts b/web-app/src/lib/completion.ts index 78a16b2ed..efdaabeb9 100644 --- a/web-app/src/lib/completion.ts +++ b/web-app/src/lib/completion.ts @@ -283,8 +283,7 @@ export const postMessageProcessing = async ( ? JSON.parse(toolCall.function.arguments) : {}, }) - // @ts-ignore - if (result.error) break + if ('error' in result && result.error) break message.metadata = { ...(message.metadata ?? {}), @@ -300,7 +299,6 @@ export const postMessageProcessing = async ( }, ], } - // @ts-ignore builder.addToolMessage(result.content[0]?.text ?? '', toolCall.id) // update message metadata return message diff --git a/web-app/src/lib/service.ts b/web-app/src/lib/service.ts index 48b81a129..defe55b64 100644 --- a/web-app/src/lib/service.ts +++ b/web-app/src/lib/service.ts @@ -27,6 +27,7 @@ export const AppRoutes = [ 'restartMcpServers', 'getConnectedServers', 'readLogs', + 'changeAppDataFolder', ] // Define API routes based on different route types export const Routes = [...CoreRoutes, ...APIRoutes, ...AppRoutes].map((r) => ({ diff --git a/web-app/src/routes/settings/general.tsx b/web-app/src/routes/settings/general.tsx index 46dc124d8..12a99a27e 100644 --- a/web-app/src/routes/settings/general.tsx +++ b/web-app/src/routes/settings/general.tsx @@ -21,7 +21,11 @@ import { DialogTitle, DialogTrigger, } from '@/components/ui/dialog' -import { factoryReset, getJanDataFolder } from '@/services/app' +import { + factoryReset, + getJanDataFolder, + relocateJanDataFolder, +} from '@/services/app' import { IconFolder } from '@tabler/icons-react' // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -115,6 +119,8 @@ function General() { if (selectedPath === janDataFolder) return if (selectedPath !== null) { setJanDataFolder(selectedPath) + await relocateJanDataFolder(selectedPath) + window.core?.api?.relaunch() // TODO: we need function to move everything into new folder selectedPath // eg like this // await window.core?.api?.moveDataFolder(selectedPath) diff --git a/web-app/src/services/app.ts b/web-app/src/services/app.ts index fc8665da7..288da8394 100644 --- a/web-app/src/services/app.ts +++ b/web-app/src/services/app.ts @@ -64,3 +64,12 @@ export const getJanDataFolder = async (): Promise => { return undefined } } + +/** + * @description This function is used to relocate the Jan data folder. + * It will change the app data folder to the specified path. + * @param path The new path for the Jan data folder + */ +export const relocateJanDataFolder = async (path: string) => { + window.core?.api?.changeAppDataFolder({ newDataFolder: path }) +} diff --git a/web-app/src/services/hardware.ts b/web-app/src/services/hardware.ts index 217bbf0c6..ab06b503a 100644 --- a/web-app/src/services/hardware.ts +++ b/web-app/src/services/hardware.ts @@ -36,7 +36,7 @@ export const setActiveGpus = async (data: { gpus: number[] }) => { } try { - const response = await extension.setAvtiveGpu(data) + const response = await extension.setActiveGpu(data) return response } catch (error) { console.error('Failed to install engine variant:', error) diff --git a/web-app/src/services/mcp.ts b/web-app/src/services/mcp.ts index 26775b9f9..116fba713 100644 --- a/web-app/src/services/mcp.ts +++ b/web-app/src/services/mcp.ts @@ -21,7 +21,7 @@ export const getTools = (): Promise => { /** * @description This function gets connected MCP servers. * @returns {Promise} The MCP names - * @returns + * @returns */ export const getConnectedServers = (): Promise => { return window.core?.api?.getConnectedServers() @@ -36,6 +36,6 @@ export const getConnectedServers = (): Promise => { export const callTool = (args: { toolName: string arguments: object -}): Promise => { +}): Promise<{ error: string; content: { text: string }[] }> => { return window.core?.api?.callTool(args) } diff --git a/web-app/src/services/providers.ts b/web-app/src/services/providers.ts index abea1c3a0..a9bd16e9f 100644 --- a/web-app/src/services/providers.ts +++ b/web-app/src/services/providers.ts @@ -77,7 +77,7 @@ export const getProviders = async (): Promise => { ? (model.capabilities as string[]) : [ModelCapabilities.COMPLETION], provider: providerName, - settings: modelSettings, + settings: modelSettings , })), } runtimeProviders.push(provider) diff --git a/web-app/src/types/app.d.ts b/web-app/src/types/app.d.ts new file mode 100644 index 000000000..d939b4503 --- /dev/null +++ b/web-app/src/types/app.d.ts @@ -0,0 +1 @@ +type Language = 'en' | 'id' | 'vn' diff --git a/web-app/src/types/global.d.ts b/web-app/src/types/global.d.ts index 040cf9679..d97e54866 100644 --- a/web-app/src/types/global.d.ts +++ b/web-app/src/types/global.d.ts @@ -1,23 +1,22 @@ -import { ExtensionManager } from '@/lib/extension' +export {} -type Language = 'en' | 'id' | 'vn' declare module 'react-syntax-highlighter-virtualized-renderer' type AppCore = { api: APIs extensionManager: ExtensionManager | undefined } + declare global { + declare const IS_TAURI: boolean + declare const IS_MACOS: boolean + declare const IS_WINDOWS: boolean + declare const IS_LINUX: boolean + declare const IS_IOS: boolean + declare const IS_ANDROID: boolean + declare const PLATFORM: string + declare const VERSION: string interface Window { core: AppCore | undefined } - - let IS_TAURI: boolean - let IS_MACOS: boolean - let IS_WINDOWS: boolean - let IS_LINUX: boolean - let IS_IOS: boolean - let IS_ANDROID: boolean - let PLATFORM: string - let VERSION: string -} \ No newline at end of file +} diff --git a/web-app/src/types/modelProviders.d.ts b/web-app/src/types/modelProviders.d.ts index a0d077953..9ee335aec 100644 --- a/web-app/src/types/modelProviders.d.ts +++ b/web-app/src/types/modelProviders.d.ts @@ -5,7 +5,7 @@ type ControllerProps = { value?: string | boolean | number placeholder?: string type?: string - options?: Array<{ value: string; name: string }> + options?: Array<{ value: number | string; name: string }> input_actions?: string[] } @@ -16,7 +16,7 @@ type ProviderSetting = { key: string title: string description: string - controller_type: 'input' | 'checkbox' | 'dropdown' | 'slider' + controller_type: 'input' | 'checkbox' | 'dropdown' | 'slider' | string controller_props: ControllerProps } @@ -31,7 +31,7 @@ type Model = { description?: string format?: string capabilities?: string[] - settings?: Record + settings?: Record } /**