diff --git a/src-tauri/src/core/fs.rs b/src-tauri/src/core/fs.rs index 4aa8e65dc..6e8f1d3a5 100644 --- a/src-tauri/src/core/fs.rs +++ b/src-tauri/src/core/fs.rs @@ -121,7 +121,6 @@ pub fn readdir_sync( } let path = resolve_path(app_handle, &args[0]); - log::error!("Reading directory: {:?}", path); let entries = fs::read_dir(&path).map_err(|e| e.to_string())?; let paths: Vec = entries .filter_map(|entry| entry.ok()) diff --git a/src-tauri/src/core/mcp.rs b/src-tauri/src/core/mcp.rs index 772f5ef5e..1ea4b06ca 100644 --- a/src-tauri/src/core/mcp.rs +++ b/src-tauri/src/core/mcp.rs @@ -9,7 +9,7 @@ use tokio::{process::Command, sync::Mutex, time::timeout}; use super::{cmd::get_jan_data_folder_path, state::AppState}; -const DEFAULT_MCP_CONFIG: &str = r#"{"mcpServers":{"browsermcp":{"command":"npx","args":["@browsermcp/mcp"],"env":{},"active":false},"fetch":{"command":"uvx","args":["mcp-server-fetch"],"env":{},"active":false},"filesystem":{"command":"npx","args":["-y","/path/to/other/allowed/dir"],"env":{},"active":false},"playwright":{"command":"npx","args":["@playwright/mcp","--isolated"],"env":{},"active":false},"mindmap":{"command":"uvx","args":["mindmap-mcp-server","--return-type","filePath"],"env":{},"active":false}}}"#; +const DEFAULT_MCP_CONFIG: &str = r#"{"mcpServers":{"browsermcp":{"command":"npx","args":["@browsermcp/mcp"],"env":{},"active":false},"fetch":{"command":"uvx","args":["mcp-server-fetch"],"env":{},"active":false},"filesystem":{"command":"npx","args":["-y","/path/to/other/allowed/dir"],"env":{},"active":false},"playwright":{"command":"npx","args":["@playwright/mcp","--isolated"],"env":{},"active":false},"sequential-thinking":{"command":"npx","args":["-y","@modelcontextprotocol/server-sequential-thinking"],"env":{},"active":false},"tavily":{"command":"npx","args":["-y","tavily-mcp"],"env":{"TAVILY_API_KEY": "tvly-YOUR_API_KEY-here"},"active":false}}}"#; // Timeout for MCP tool calls (30 seconds) const MCP_TOOL_CALL_TIMEOUT: Duration = Duration::from_secs(30); diff --git a/web-app/src/containers/ModelSetting.tsx b/web-app/src/containers/ModelSetting.tsx index e7a9d0d08..833d1423f 100644 --- a/web-app/src/containers/ModelSetting.tsx +++ b/web-app/src/containers/ModelSetting.tsx @@ -1,4 +1,5 @@ import { IconSettings } from '@tabler/icons-react' +import debounce from 'lodash.debounce' import { Sheet, @@ -10,7 +11,7 @@ import { } from '@/components/ui/sheet' import { DynamicControllerSetting } from '@/containers/dynamicControllerSetting' import { useModelProvider } from '@/hooks/useModelProvider' -import { updateModel } from '@/services/models' +import { updateModel, stopModel } from '@/services/models' import { ModelSettingParams } from '@janhq/core' type ModelSettingProps = { @@ -21,6 +22,11 @@ type ModelSettingProps = { export function ModelSetting({ model, provider }: ModelSettingProps) { const { updateProvider } = useModelProvider() + // Create a debounced version of stopModel that waits 500ms after the last call + const debouncedStopModel = debounce((modelId: string) => { + stopModel(modelId) + }, 500) + const handleSettingChange = ( key: string, value: string | boolean | number @@ -66,11 +72,15 @@ export function ModelSetting({ model, provider }: ModelSettingProps) { }, {} as Record ) as ModelSettingParams + updateModel({ id: model.id, settings: params, ...(params as unknown as object), }) + + // Call debounced stopModel after updating the model + debouncedStopModel(model.id) } } diff --git a/web-app/src/hooks/useAppUpdater.ts b/web-app/src/hooks/useAppUpdater.ts index 22e860f6b..fb24c6600 100644 --- a/web-app/src/hooks/useAppUpdater.ts +++ b/web-app/src/hooks/useAppUpdater.ts @@ -2,6 +2,9 @@ import { isDev } from '@/lib/utils' import { check, Update } from '@tauri-apps/plugin-updater' import { useState, useCallback, useEffect } from 'react' import { events, AppEvent } from '@janhq/core' +import { emit } from '@tauri-apps/api/event' +import { SystemEvent } from '@/types/events' +import { stopAllModels } from '@/services/models' export interface UpdateState { isUpdateAvailable: boolean @@ -155,6 +158,9 @@ export const useAppUpdater = () => { let downloaded = 0 let contentLength = 0 + await stopAllModels() + emit(SystemEvent.KILL_SIDECAR) + await new Promise((resolve) => setTimeout(resolve, 1000)) await updateState.updateInfo.downloadAndInstall((event) => { switch (event.event) { diff --git a/web-app/src/routes/hub.tsx b/web-app/src/routes/hub.tsx index 63d719648..c11e3d8f1 100644 --- a/web-app/src/routes/hub.tsx +++ b/web-app/src/routes/hub.tsx @@ -82,9 +82,6 @@ function Hub() { [modelId]: !prev[modelId], })) } - useEffect(() => { - fetchModelHub().then(fetchSources) - }, [fetchSources]) useEffect(() => { if (search.repo) { @@ -150,6 +147,7 @@ function Hub() { }, [searchValue, sortedModels, showOnlyDownloaded, llamaProvider?.models]) useEffect(() => { + fetchModelHub() fetchSources() }, [fetchSources]) diff --git a/web-app/src/routes/settings/general.tsx b/web-app/src/routes/settings/general.tsx index ae7f45576..572d7f916 100644 --- a/web-app/src/routes/settings/general.tsx +++ b/web-app/src/routes/settings/general.tsx @@ -44,6 +44,7 @@ import { toast } from 'sonner' import { isDev } from '@/lib/utils' import { emit } from '@tauri-apps/api/event' import { stopAllModels } from '@/services/models' +import { SystemEvent } from '@/types/events' // eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.settings.general as any)({ @@ -149,7 +150,7 @@ function General() { if (selectedNewPath) { try { await stopAllModels() - emit('kill-sidecar') + emit(SystemEvent.KILL_SIDECAR) setTimeout(async () => { try { await relocateJanDataFolder(selectedNewPath) diff --git a/web-app/src/services/app.ts b/web-app/src/services/app.ts index 487039cb6..ee43ac663 100644 --- a/web-app/src/services/app.ts +++ b/web-app/src/services/app.ts @@ -2,6 +2,7 @@ import { AppConfiguration, fs } from '@janhq/core' import { invoke } from '@tauri-apps/api/core' import { emit } from '@tauri-apps/api/event' import { stopAllModels } from './models' +import { SystemEvent } from '@/types/events' /** * @description This function is used to reset the app to its factory settings. @@ -11,7 +12,7 @@ import { stopAllModels } from './models' export const factoryReset = async () => { // Kill background processes and remove data folder await stopAllModels() - emit('kill-sidecar') + emit(SystemEvent.KILL_SIDECAR) setTimeout(async () => { const janDataFolderPath = await getJanDataFolder() if (janDataFolderPath) await fs.rm(janDataFolderPath) diff --git a/web-app/src/types/events.ts b/web-app/src/types/events.ts index a682ece9b..673bb28b9 100644 --- a/web-app/src/types/events.ts +++ b/web-app/src/types/events.ts @@ -1,3 +1,4 @@ export enum SystemEvent { MCP_UPDATE = 'mcp-update', + KILL_SIDECAR = 'kill-sidecar', }