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
This commit is contained in:
Louis 2025-05-21 10:48:10 +07:00 committed by GitHub
parent e4168a4c17
commit 6676e0ced8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 106 additions and 37 deletions

View File

@ -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[]
}>

View File

@ -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[]
}> {

View File

@ -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",

View File

@ -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(&current_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<String> {
state.app_token.clone()

View File

@ -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,

View File

@ -20,7 +20,6 @@ export default function LanguageSwitcher() {
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng)
// @ts-ignore
setCurrentLanguage(lng as Language)
}

View File

@ -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,
})

View File

@ -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({

View File

@ -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

View File

@ -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
}

View File

@ -182,7 +182,7 @@ export const useHardware = create<HardwareStore>()(
}
}
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: {

View File

@ -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

View File

@ -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) => ({

View File

@ -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)

View File

@ -64,3 +64,12 @@ export const getJanDataFolder = async (): Promise<string | undefined> => {
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 })
}

View File

@ -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)

View File

@ -36,6 +36,6 @@ export const getConnectedServers = (): Promise<string[]> => {
export const callTool = (args: {
toolName: string
arguments: object
}): Promise<unknown> => {
}): Promise<{ error: string; content: { text: string }[] }> => {
return window.core?.api?.callTool(args)
}

1
web-app/src/types/app.d.ts vendored Normal file
View File

@ -0,0 +1 @@
type Language = 'en' | 'id' | 'vn'

View File

@ -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
}

View File

@ -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<string, unknown>
settings?: Record<string, ProviderSetting>
}
/**