From ecef9d7df63f4182a7682ff41b77f3b017ebdeca Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 3 Jun 2025 01:09:36 +0700 Subject: [PATCH] feat: handle open Jan on HF GGUF repo (#5173) * feat: handle open Jan on HF GGUF repo * chore: reset retry attempts --- .../inference-cortex-extension/src/index.ts | 2 +- src-tauri/Cargo.toml | 2 ++ src-tauri/Info.plist | 19 +++++++++++ src-tauri/capabilities/default.json | 1 + src-tauri/src/lib.rs | 12 +++++-- src-tauri/tauri.conf.json | 3 +- web-app/package.json | 1 + web-app/src/providers/DataProvider.tsx | 31 ++++++++++++++++++ web-app/src/routes/hub.tsx | 32 +++++++++++++++++-- 9 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src-tauri/Info.plist diff --git a/extensions/inference-cortex-extension/src/index.ts b/extensions/inference-cortex-extension/src/index.ts index c453281c5..7d3cf0c4d 100644 --- a/extensions/inference-cortex-extension/src/index.ts +++ b/extensions/inference-cortex-extension/src/index.ts @@ -90,7 +90,7 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { Authorization: `Bearer ${apiKey}`, } : {}, - retry: 4, + retry: 10, }) return this.api } diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ef8932d4e..992460ff8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -51,6 +51,7 @@ dirs = "6.0.0" sysinfo = "0.34.2" ash = "0.38.0" nvml-wrapper = "0.10.0" +tauri-plugin-deep-link = "2" [target.'cfg(windows)'.dependencies] libloading = "0.8.7" @@ -59,3 +60,4 @@ libc = "0.2.172" [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] tauri-plugin-updater = "2" once_cell = "1.18" +tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] } diff --git a/src-tauri/Info.plist b/src-tauri/Info.plist new file mode 100644 index 000000000..b77d0febd --- /dev/null +++ b/src-tauri/Info.plist @@ -0,0 +1,19 @@ + + + + + CFBundleIdentifier + jan.ai.app + CFBundleURLTypes + + + CFBundleURLName + jan.ai.app + CFBundleURLSchemes + + jan + + + + + diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 566118a7d..9c5b69d46 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -19,6 +19,7 @@ "log:default", "updater:default", "dialog:default", + "deep-link:default", "core:webview:allow-create-webview-window", { "identifier": "http:default", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 23343bc43..436e4c0ba 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -14,8 +14,16 @@ use reqwest::blocking::Client; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_os::init()) + let mut builder = tauri::Builder::default(); + #[cfg(desktop)] + { + builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| { + println!("a new app instance was opened with {argv:?} and the deep link event was already triggered"); + // when defining deep link schemes at runtime, you must also check `argv` here + })); + } + builder.plugin(tauri_plugin_os::init()) + .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_http::init()) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 18108d34e..545873172 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -68,7 +68,8 @@ "windows": { "installMode": "passive" } - } + }, + "deep-link": { "schemes": ["jan"] } }, "bundle": { "active": true, diff --git a/web-app/package.json b/web-app/package.json index d0941dd2b..ad82e5688 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -30,6 +30,7 @@ "@tanstack/react-router": "^1.116.0", "@tanstack/react-router-devtools": "^1.116.0", "@tauri-apps/api": "^2.5.0", + "@tauri-apps/plugin-deep-link": "~2", "@tauri-apps/plugin-dialog": "^2.2.1", "@tauri-apps/plugin-opener": "^2.2.7", "@tauri-apps/plugin-os": "^2.2.1", diff --git a/web-app/src/providers/DataProvider.tsx b/web-app/src/providers/DataProvider.tsx index 9e2f50d00..49d9dcf4b 100644 --- a/web-app/src/providers/DataProvider.tsx +++ b/web-app/src/providers/DataProvider.tsx @@ -13,6 +13,12 @@ import { getMCPConfig } from '@/services/mcp' import { useAssistant } from '@/hooks/useAssistant' import { getAssistants } from '@/services/assistants' import { migrateData } from '@/utils/migration' +import { + onOpenUrl, + getCurrent as getCurrentDeepLinkUrls, +} from '@tauri-apps/plugin-deep-link' +import { useNavigate } from '@tanstack/react-router' +import { route } from '@/constants/routes' export function DataProvider() { const { setProviders } = useModelProvider() @@ -21,6 +27,7 @@ export function DataProvider() { const { checkForUpdate } = useAppUpdater() const { setServers } = useMCPServers() const { setAssistants } = useAssistant() + const navigate = useNavigate() useEffect(() => { fetchModels().then((models) => { @@ -39,6 +46,8 @@ export function DataProvider() { console.warn('Failed to load assistants, keeping default:', error) }) migrateData() + getCurrentDeepLinkUrls().then(handleDeepLink) + onOpenUrl(handleDeepLink) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -58,5 +67,27 @@ export function DataProvider() { checkForUpdate() }, [checkForUpdate]) + const handleDeepLink = (urls: string[] | null) => { + if (!urls) return + console.log('Received deeplink:', urls) + const deeplink = urls[0] + if (deeplink) { + const url = new URL(deeplink) + const params = url.pathname.split('/').filter((str) => str.length > 0) + + if (params.length < 3) return undefined + // const action = params[0] + // const provider = params[1] + const resource = params.slice(1).join('/') + // return { action, provider, resource } + navigate({ + to: route.hub, + search: { + repo: resource, + }, + }) + } + } + return null } diff --git a/web-app/src/routes/hub.tsx b/web-app/src/routes/hub.tsx index ec58f9252..ecff4276d 100644 --- a/web-app/src/routes/hub.tsx +++ b/web-app/src/routes/hub.tsx @@ -1,4 +1,10 @@ -import { createFileRoute, Link, useNavigate } from '@tanstack/react-router' +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + createFileRoute, + Link, + useNavigate, + useSearch, +} from '@tanstack/react-router' import { route } from '@/constants/routes' import { useModelSources } from '@/hooks/useModelSources' import { cn, fuzzySearch, toGigabytes } from '@/lib/utils' @@ -37,10 +43,15 @@ type ModelProps = { }[] } } +type SearchParams = { + repo: string +} -// eslint-disable-next-line @typescript-eslint/no-explicit-any export const Route = createFileRoute(route.hub as any)({ component: Hub, + validateSearch: (search: Record): SearchParams => ({ + repo: search.repo as SearchParams['repo'], + }), }) const sortOptions = [ @@ -50,6 +61,7 @@ const sortOptions = [ function Hub() { const { sources, fetchSources, loading } = useModelSources() + const search = useSearch({ from: route.hub as any }) const [searchValue, setSearchValue] = useState('') const [sortSelected, setSortSelected] = useState('newest') const [expandedModels, setExpandedModels] = useState>( @@ -71,6 +83,22 @@ function Hub() { })) } + useEffect(() => { + if (search.repo) { + setSearchValue(search.repo || '') + setIsSearching(true) + addModelSourceTimeoutRef.current = setTimeout(() => { + addModelSource(search.repo) + .then(() => { + fetchSources() + }) + .finally(() => { + setIsSearching(false) + }) + }, 500) + } + }, [fetchSources, search]) + // Sorting functionality const sortedModels = useMemo(() => { return [...sources].sort((a, b) => {