From 7f0c605651a16026f77de94b8744a8e14472d1d6 Mon Sep 17 00:00:00 2001 From: Louis Date: Fri, 1 Aug 2025 13:32:41 +0700 Subject: [PATCH 1/5] fix: Jan hub repo detail and deep link --- web-app/src/providers/DataProvider.tsx | 2 +- web-app/src/routes/hub/$modelId.tsx | 40 ++++++++++-- web-app/src/routes/hub/index.tsx | 90 +++----------------------- web-app/src/services/models.ts | 41 ++++++++++++ 4 files changed, 86 insertions(+), 87 deletions(-) diff --git a/web-app/src/providers/DataProvider.tsx b/web-app/src/providers/DataProvider.tsx index 9fdb802ea..dcfacd536 100644 --- a/web-app/src/providers/DataProvider.tsx +++ b/web-app/src/providers/DataProvider.tsx @@ -79,7 +79,7 @@ export function DataProvider() { const resource = params.slice(1).join('/') // return { action, provider, resource } navigate({ - to: route.hub.index, + to: route.hub.model, search: { repo: resource, }, diff --git a/web-app/src/routes/hub/$modelId.tsx b/web-app/src/routes/hub/$modelId.tsx index 245909174..679b265de 100644 --- a/web-app/src/routes/hub/$modelId.tsx +++ b/web-app/src/routes/hub/$modelId.tsx @@ -1,5 +1,10 @@ import HeaderPage from '@/containers/HeaderPage' -import { createFileRoute, useParams, useNavigate } from '@tanstack/react-router' +import { + createFileRoute, + useParams, + useNavigate, + useSearch, +} from '@tanstack/react-router' import { IconArrowLeft, IconDownload, @@ -13,23 +18,38 @@ import { RenderMarkdown } from '@/containers/RenderMarkdown' import { useEffect, useMemo, useCallback, useState } from 'react' import { useModelProvider } from '@/hooks/useModelProvider' import { useDownloadStore } from '@/hooks/useDownloadStore' -import { pullModel } from '@/services/models' +import { + CatalogModel, + convertHfRepoToCatalogModel, + fetchHuggingFaceRepo, + pullModel, +} from '@/services/models' import { Progress } from '@/components/ui/progress' import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' +type SearchParams = { + repo: string +} + export const Route = createFileRoute('/hub/$modelId')({ component: HubModelDetail, + validateSearch: (search: Record): SearchParams => ({ + repo: search.repo as SearchParams['repo'], + }), }) function HubModelDetail() { const { modelId } = useParams({ from: Route.id }) const navigate = useNavigate() const { sources, fetchSources } = useModelSources() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const search = useSearch({ from: route.hub.model as any }) const { getProviderByName } = useModelProvider() const llamaProvider = getProviderByName('llamacpp') const { downloads, localDownloadingModels, addLocalDownloadingModel } = useDownloadStore() + const [repoData, setRepoData] = useState() // State for README content const [readmeContent, setReadmeContent] = useState('') @@ -39,10 +59,21 @@ function HubModelDetail() { fetchSources() }, [fetchSources]) + const fetchRepo = useCallback(async () => { + const repoInfo = await fetchHuggingFaceRepo(search.repo || modelId) + if (repoInfo) { + const repoDetail = convertHfRepoToCatalogModel(repoInfo) + setRepoData(repoDetail) + } + }, [modelId, search]) + + useEffect(() => { + fetchRepo() + }, [modelId, fetchRepo]) // Find the model data from sources const modelData = useMemo(() => { - return sources.find((model) => model.model_name === modelId) - }, [sources, modelId]) + return sources.find((model) => model.model_name === modelId) ?? repoData + }, [sources, modelId, repoData]) // Download processes const downloadProcesses = useMemo( @@ -116,7 +147,6 @@ function HubModelDetail() { }) }, [modelData]) - // Fetch README content when modelData.readme is available useEffect(() => { if (modelData?.readme) { diff --git a/web-app/src/routes/hub/index.tsx b/web-app/src/routes/hub/index.tsx index 7c904d1ee..66e079412 100644 --- a/web-app/src/routes/hub/index.tsx +++ b/web-app/src/routes/hub/index.tsx @@ -31,7 +31,7 @@ import { CatalogModel, pullModel, fetchHuggingFaceRepo, - HuggingFaceRepo, + convertHfRepoToCatalogModel, } from '@/services/models' import { useDownloadStore } from '@/hooks/useDownloadStore' import { Progress } from '@/components/ui/progress' @@ -63,14 +63,16 @@ function Hub() { { value: 'newest', name: t('hub:sortNewest') }, { value: 'most-downloaded', name: t('hub:sortMostDownloaded') }, ] - const searchOptions = { - includeScore: true, - // Search in `author` and in `tags` array - keys: ['model_name', 'quants.model_id'], - } + const searchOptions = useMemo(() => { + return { + includeScore: true, + // Search in `author` and in `tags` array + keys: ['model_name', 'quants.model_id'], + } + }, []) const { sources, addSource, fetchSources, loading } = useModelSources() - const search = useSearch({ from: route.hub.index as any }) + const [searchValue, setSearchValue] = useState('') const [sortSelected, setSortSelected] = useState('newest') const [expandedModels, setExpandedModels] = useState>( @@ -92,48 +94,6 @@ function Hub() { const { getProviderByName } = useModelProvider() const llamaProvider = getProviderByName('llamacpp') - // Convert HuggingFace repository to CatalogModel format - const convertHfRepoToCatalogModel = useCallback( - (repo: HuggingFaceRepo): CatalogModel => { - // Extract GGUF files from the repository siblings - const ggufFiles = - repo.siblings?.filter((file) => - file.rfilename.toLowerCase().endsWith('.gguf') - ) || [] - - // Convert GGUF files to quants format - const quants = ggufFiles.map((file) => { - // Format file size - const formatFileSize = (size?: number) => { - if (!size) return 'Unknown size' - if (size < 1024 ** 3) return `${(size / 1024 ** 2).toFixed(1)} MB` - return `${(size / 1024 ** 3).toFixed(1)} GB` - } - - // Generate model_id from filename (remove .gguf extension, case-insensitive) - const modelId = file.rfilename.replace(/\.gguf$/i, '') - - return { - model_id: modelId, - path: `https://huggingface.co/${repo.modelId}/resolve/main/${file.rfilename}`, - file_size: formatFileSize(file.size), - } - }) - - return { - model_name: repo.modelId, - description: `**Metadata:** ${repo.pipeline_tag}\n\n **Tags**: ${repo.tags?.join(', ')}`, - developer: repo.author, - downloads: repo.downloads || 0, - num_quants: quants.length, - quants: quants, - created_at: repo.created_at, - readme: `https://huggingface.co/${repo.modelId}/resolve/main/README.md`, - } - }, - [] - ) - const toggleModelExpansion = (modelId: string) => { setExpandedModels((prev) => ({ ...prev, @@ -141,35 +101,6 @@ function Hub() { })) } - useEffect(() => { - if (search.repo) { - setSearchValue(search.repo || '') - setIsSearching(true) - - addModelSourceTimeoutRef.current = setTimeout(async () => { - try { - // Fetch HuggingFace repository information - const repoInfo = await fetchHuggingFaceRepo(search.repo) - if (repoInfo) { - const catalogModel = convertHfRepoToCatalogModel(repoInfo) - if ( - !sources.some((s) => s.model_name === catalogModel.model_name) - ) { - setHuggingFaceRepo(catalogModel) - addSource(catalogModel) - } - } - - await fetchSources() - } catch (error) { - console.error('Error fetching repository info:', error) - } finally { - setIsSearching(false) - } - }, 500) - } - }, [convertHfRepoToCatalogModel, fetchSources, addSource, search, sources]) - // Sorting functionality const sortedModels = useMemo(() => { return [...sources].sort((a, b) => { @@ -264,9 +195,6 @@ function Hub() { addSource(catalogModel) } } - - // Original addSource logic (if needed) - await fetchSources() } catch (error) { console.error('Error fetching repository info:', error) } finally { diff --git a/web-app/src/services/models.ts b/web-app/src/services/models.ts index 71911244f..12bf1997d 100644 --- a/web-app/src/services/models.ts +++ b/web-app/src/services/models.ts @@ -134,6 +134,47 @@ export const fetchHuggingFaceRepo = async ( } } +// Convert HuggingFace repository to CatalogModel format +export const convertHfRepoToCatalogModel = ( + repo: HuggingFaceRepo +): CatalogModel => { + // Extract GGUF files from the repository siblings + const ggufFiles = + repo.siblings?.filter((file) => + file.rfilename.toLowerCase().endsWith('.gguf') + ) || [] + + // Convert GGUF files to quants format + const quants = ggufFiles.map((file) => { + // Format file size + const formatFileSize = (size?: number) => { + if (!size) return 'Unknown size' + if (size < 1024 ** 3) return `${(size / 1024 ** 2).toFixed(1)} MB` + return `${(size / 1024 ** 3).toFixed(1)} GB` + } + + // Generate model_id from filename (remove .gguf extension, case-insensitive) + const modelId = file.rfilename.replace(/\.gguf$/i, '') + + return { + model_id: modelId, + path: `https://huggingface.co/${repo.modelId}/resolve/main/${file.rfilename}`, + file_size: formatFileSize(file.size), + } + }) + + return { + model_name: repo.modelId, + description: `**Tags**: ${repo.tags?.join(', ')}`, + developer: repo.author, + downloads: repo.downloads || 0, + num_quants: quants.length, + quants: quants, + created_at: repo.created_at, + readme: `https://huggingface.co/${repo.modelId}/resolve/main/README.md`, + } +} + /** * Updates a model. * @param model The model to update. From 90e46a2696f2d79ca75b588df3f91f8a7ea920be Mon Sep 17 00:00:00 2001 From: Louis Date: Fri, 1 Aug 2025 13:50:07 +0700 Subject: [PATCH 2/5] test: add tests --- web-app/src/routes/hub/$modelId.tsx | 2 +- web-app/src/services/__tests__/models.test.ts | 313 +++++++++++++++++- 2 files changed, 308 insertions(+), 7 deletions(-) diff --git a/web-app/src/routes/hub/$modelId.tsx b/web-app/src/routes/hub/$modelId.tsx index 679b265de..d46c20ca2 100644 --- a/web-app/src/routes/hub/$modelId.tsx +++ b/web-app/src/routes/hub/$modelId.tsx @@ -44,7 +44,7 @@ function HubModelDetail() { const navigate = useNavigate() const { sources, fetchSources } = useModelSources() // eslint-disable-next-line @typescript-eslint/no-explicit-any - const search = useSearch({ from: route.hub.model as any }) + const search = useSearch({ from: Route.id as any }) const { getProviderByName } = useModelProvider() const llamaProvider = getProviderByName('llamacpp') const { downloads, localDownloadingModels, addLocalDownloadingModel } = diff --git a/web-app/src/services/__tests__/models.test.ts b/web-app/src/services/__tests__/models.test.ts index c6f626911..b648b2677 100644 --- a/web-app/src/services/__tests__/models.test.ts +++ b/web-app/src/services/__tests__/models.test.ts @@ -4,6 +4,7 @@ import { fetchModels, fetchModelCatalog, fetchHuggingFaceRepo, + convertHfRepoToCatalogModel, updateModel, pullModel, abortDownload, @@ -12,6 +13,8 @@ import { stopModel, stopAllModels, startModel, + HuggingFaceRepo, + CatalogModel, } from '../models' import { EngineManager, Model } from '@janhq/core' @@ -334,7 +337,9 @@ describe('models service', () => { }) // Test with full URL - await fetchHuggingFaceRepo('https://huggingface.co/microsoft/DialoGPT-medium') + await fetchHuggingFaceRepo( + 'https://huggingface.co/microsoft/DialoGPT-medium' + ) expect(fetch).toHaveBeenCalledWith( 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true' ) @@ -380,7 +385,7 @@ describe('models service', () => { it('should handle other HTTP errors', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - + ;(fetch as any).mockResolvedValue({ ok: false, status: 500, @@ -394,13 +399,13 @@ describe('models service', () => { 'Error fetching HuggingFace repository:', expect.any(Error) ) - + consoleSpy.mockRestore() }) it('should handle network errors', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - + ;(fetch as any).mockRejectedValue(new Error('Network error')) const result = await fetchHuggingFaceRepo('microsoft/DialoGPT-medium') @@ -410,7 +415,7 @@ describe('models service', () => { 'Error fetching HuggingFace repository:', expect.any(Error) ) - + consoleSpy.mockRestore() }) @@ -524,7 +529,303 @@ describe('models service', () => { expect(result).toEqual(mockRepoData) // Verify the GGUF file is present in siblings - expect(result?.siblings?.some(s => s.rfilename.endsWith('.gguf'))).toBe(true) + expect(result?.siblings?.some((s) => s.rfilename.endsWith('.gguf'))).toBe( + true + ) + }) + }) + + describe('convertHfRepoToCatalogModel', () => { + const mockHuggingFaceRepo: HuggingFaceRepo = { + id: 'microsoft/DialoGPT-medium', + modelId: 'microsoft/DialoGPT-medium', + sha: 'abc123', + downloads: 1500, + likes: 75, + tags: ['pytorch', 'transformers', 'text-generation'], + pipeline_tag: 'text-generation', + created_at: '2021-01-01T00:00:00Z', + last_modified: '2021-12-01T00:00:00Z', + private: false, + disabled: false, + gated: false, + author: 'microsoft', + siblings: [ + { + rfilename: 'model-q4_0.gguf', + size: 2 * 1024 * 1024 * 1024, // 2GB + blobId: 'blob123', + }, + { + rfilename: 'model-q8_0.GGUF', // Test case-insensitive matching + size: 4 * 1024 * 1024 * 1024, // 4GB + blobId: 'blob456', + }, + { + rfilename: 'tokenizer.json', // Non-GGUF file (should be filtered out) + size: 1024 * 1024, // 1MB + blobId: 'blob789', + }, + ], + } + + it('should convert HuggingFace repo to catalog model format', () => { + const result = convertHfRepoToCatalogModel(mockHuggingFaceRepo) + + const expected: CatalogModel = { + model_name: 'microsoft/DialoGPT-medium', + description: '**Tags**: pytorch, transformers, text-generation', + developer: 'microsoft', + downloads: 1500, + num_quants: 2, + quants: [ + { + model_id: 'model-q4_0', + path: 'https://huggingface.co/microsoft/DialoGPT-medium/resolve/main/model-q4_0.gguf', + file_size: '2.0 GB', + }, + { + model_id: 'model-q8_0', + path: 'https://huggingface.co/microsoft/DialoGPT-medium/resolve/main/model-q8_0.GGUF', + file_size: '4.0 GB', + }, + ], + created_at: '2021-01-01T00:00:00Z', + readme: + 'https://huggingface.co/microsoft/DialoGPT-medium/resolve/main/README.md', + } + + expect(result).toEqual(expected) + }) + + it('should handle repository with no GGUF files', () => { + const repoWithoutGGUF: HuggingFaceRepo = { + ...mockHuggingFaceRepo, + siblings: [ + { + rfilename: 'tokenizer.json', + size: 1024 * 1024, + blobId: 'blob789', + }, + { + rfilename: 'config.json', + size: 2048, + blobId: 'blob101', + }, + ], + } + + const result = convertHfRepoToCatalogModel(repoWithoutGGUF) + + expect(result.num_quants).toBe(0) + expect(result.quants).toEqual([]) + }) + + it('should handle repository with no siblings', () => { + const repoWithoutSiblings: HuggingFaceRepo = { + ...mockHuggingFaceRepo, + siblings: undefined, + } + + const result = convertHfRepoToCatalogModel(repoWithoutSiblings) + + expect(result.num_quants).toBe(0) + expect(result.quants).toEqual([]) + }) + + it('should format file sizes correctly', () => { + const repoWithVariousFileSizes: HuggingFaceRepo = { + ...mockHuggingFaceRepo, + siblings: [ + { + rfilename: 'small-model.gguf', + size: 500 * 1024 * 1024, // 500MB + blobId: 'blob1', + }, + { + rfilename: 'large-model.gguf', + size: 3.5 * 1024 * 1024 * 1024, // 3.5GB + blobId: 'blob2', + }, + { + rfilename: 'unknown-size.gguf', + // No size property + blobId: 'blob3', + }, + ], + } + + const result = convertHfRepoToCatalogModel(repoWithVariousFileSizes) + + expect(result.quants[0].file_size).toBe('500.0 MB') + expect(result.quants[1].file_size).toBe('3.5 GB') + expect(result.quants[2].file_size).toBe('Unknown size') + }) + + it('should handle empty or undefined tags', () => { + const repoWithEmptyTags: HuggingFaceRepo = { + ...mockHuggingFaceRepo, + tags: [], + } + + const result = convertHfRepoToCatalogModel(repoWithEmptyTags) + + expect(result.description).toBe('**Tags**: ') + }) + + it('should handle missing downloads count', () => { + const repoWithoutDownloads: HuggingFaceRepo = { + ...mockHuggingFaceRepo, + downloads: undefined as any, + } + + const result = convertHfRepoToCatalogModel(repoWithoutDownloads) + + expect(result.downloads).toBe(0) + }) + + it('should correctly remove .gguf extension from model IDs', () => { + const repoWithVariousGGUF: HuggingFaceRepo = { + ...mockHuggingFaceRepo, + siblings: [ + { + rfilename: 'model.gguf', + size: 1024, + blobId: 'blob1', + }, + { + rfilename: 'MODEL.GGUF', + size: 1024, + blobId: 'blob2', + }, + { + rfilename: 'complex-model-name.gguf', + size: 1024, + blobId: 'blob3', + }, + ], + } + + const result = convertHfRepoToCatalogModel(repoWithVariousGGUF) + + expect(result.quants[0].model_id).toBe('model') + expect(result.quants[1].model_id).toBe('MODEL') + expect(result.quants[2].model_id).toBe('complex-model-name') + }) + + it('should generate correct download paths', () => { + const result = convertHfRepoToCatalogModel(mockHuggingFaceRepo) + + expect(result.quants[0].path).toBe( + 'https://huggingface.co/microsoft/DialoGPT-medium/resolve/main/model-q4_0.gguf' + ) + expect(result.quants[1].path).toBe( + 'https://huggingface.co/microsoft/DialoGPT-medium/resolve/main/model-q8_0.GGUF' + ) + }) + + it('should generate correct readme URL', () => { + const result = convertHfRepoToCatalogModel(mockHuggingFaceRepo) + + expect(result.readme).toBe( + 'https://huggingface.co/microsoft/DialoGPT-medium/resolve/main/README.md' + ) + }) + + it('should handle GGUF files with case-insensitive extension matching', () => { + const repoWithMixedCase: HuggingFaceRepo = { + ...mockHuggingFaceRepo, + siblings: [ + { + rfilename: 'model-1.gguf', + size: 1024, + blobId: 'blob1', + }, + { + rfilename: 'model-2.GGUF', + size: 1024, + blobId: 'blob2', + }, + { + rfilename: 'model-3.GgUf', + size: 1024, + blobId: 'blob3', + }, + { + rfilename: 'not-a-model.txt', + size: 1024, + blobId: 'blob4', + }, + ], + } + + const result = convertHfRepoToCatalogModel(repoWithMixedCase) + + expect(result.num_quants).toBe(3) + expect(result.quants).toHaveLength(3) + expect(result.quants[0].model_id).toBe('model-1') + expect(result.quants[1].model_id).toBe('model-2') + expect(result.quants[2].model_id).toBe('model-3') + }) + + it('should handle edge cases with file size formatting', () => { + const repoWithEdgeCases: HuggingFaceRepo = { + ...mockHuggingFaceRepo, + siblings: [ + { + rfilename: 'tiny.gguf', + size: 512, // < 1MB + blobId: 'blob1', + }, + { + rfilename: 'exactly-1gb.gguf', + size: 1024 * 1024 * 1024, // Exactly 1GB + blobId: 'blob2', + }, + { + rfilename: 'zero-size.gguf', + size: 0, + blobId: 'blob3', + }, + ], + } + + const result = convertHfRepoToCatalogModel(repoWithEdgeCases) + + expect(result.quants[0].file_size).toBe('0.0 MB') + expect(result.quants[1].file_size).toBe('1.0 GB') + expect(result.quants[2].file_size).toBe('Unknown size') // 0 is falsy, so it returns 'Unknown size' + }) + + it('should handle missing optional fields gracefully', () => { + const minimalRepo: HuggingFaceRepo = { + id: 'minimal/repo', + modelId: 'minimal/repo', + sha: 'abc123', + downloads: 0, + likes: 0, + tags: [], + created_at: '2021-01-01T00:00:00Z', + last_modified: '2021-12-01T00:00:00Z', + private: false, + disabled: false, + gated: false, + author: 'minimal', + siblings: [ + { + rfilename: 'model.gguf', + blobId: 'blob1', + }, + ], + } + + const result = convertHfRepoToCatalogModel(minimalRepo) + + expect(result.model_name).toBe('minimal/repo') + expect(result.developer).toBe('minimal') + expect(result.downloads).toBe(0) + expect(result.description).toBe('**Tags**: ') + expect(result.quants[0].file_size).toBe('Unknown size') }) }) }) From b8070f1871a562f9bf1412ff254b2c41b99c294f Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 3 Aug 2025 23:32:27 +0700 Subject: [PATCH 3/5] chore: able to disable updater via env flag --- web-app/src/providers/DataProvider.tsx | 7 ++++++- web-app/src/types/global.d.ts | 1 + web-app/vite.config.ts | 13 ++++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/web-app/src/providers/DataProvider.tsx b/web-app/src/providers/DataProvider.tsx index dcfacd536..6110f9dd5 100644 --- a/web-app/src/providers/DataProvider.tsx +++ b/web-app/src/providers/DataProvider.tsx @@ -62,7 +62,12 @@ export function DataProvider() { // Check for app updates useEffect(() => { - checkForUpdate() + // Only check for updates if the auto updater is not disabled + // App might be distributed via other package managers + // or methods that handle updates differently + if (!AUTO_UPDATER_DISABLED) { + checkForUpdate() + } }, [checkForUpdate]) const handleDeepLink = (urls: string[] | null) => { diff --git a/web-app/src/types/global.d.ts b/web-app/src/types/global.d.ts index fe33f3d46..b104314b0 100644 --- a/web-app/src/types/global.d.ts +++ b/web-app/src/types/global.d.ts @@ -19,6 +19,7 @@ declare global { declare const POSTHOG_KEY: string declare const POSTHOG_HOST: string declare const MODEL_CATALOG_URL: string + declare const AUTO_UPDATER_DISABLED: boolean interface Window { core: AppCore | undefined } diff --git a/web-app/vite.config.ts b/web-app/vite.config.ts index 697c1a64f..4c1b2ab40 100644 --- a/web-app/vite.config.ts +++ b/web-app/vite.config.ts @@ -33,19 +33,19 @@ export default defineConfig(({ mode }) => { define: { IS_TAURI: JSON.stringify(process.env.IS_TAURI), IS_MACOS: JSON.stringify( - process.env.TAURI_ENV_PLATFORM?.includes('darwin') ?? 'false' + process.env.TAURI_ENV_PLATFORM?.includes('darwin') ?? false ), IS_WINDOWS: JSON.stringify( - process.env.TAURI_ENV_PLATFORM?.includes('windows') ?? 'false' + process.env.TAURI_ENV_PLATFORM?.includes('windows') ?? false ), IS_LINUX: JSON.stringify( - process.env.TAURI_ENV_PLATFORM?.includes('linux') ?? 'false' + process.env.TAURI_ENV_PLATFORM?.includes('linux') ?? false ), IS_IOS: JSON.stringify( - process.env.TAURI_ENV_PLATFORM?.includes('ios') ?? 'false' + process.env.TAURI_ENV_PLATFORM?.includes('ios') ?? false ), IS_ANDROID: JSON.stringify( - process.env.TAURI_ENV_PLATFORM?.includes('android') ?? 'false' + process.env.TAURI_ENV_PLATFORM?.includes('android') ?? false ), PLATFORM: JSON.stringify(process.env.TAURI_ENV_PLATFORM), @@ -56,6 +56,9 @@ export default defineConfig(({ mode }) => { MODEL_CATALOG_URL: JSON.stringify( 'https://raw.githubusercontent.com/menloresearch/model-catalog/main/model_catalog.json' ), + AUTO_UPDATER_DISABLED: JSON.stringify( + env.AUTO_UPDATER_DISABLED === 'true' + ), }, // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` From 065a850a94802c7ea279cb3b4bd58b1f82a2855f Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 4 Aug 2025 11:10:47 +0700 Subject: [PATCH 4/5] fix: test env --- web-app/vitest.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web-app/vitest.config.ts b/web-app/vitest.config.ts index e30c4d545..c2289f337 100644 --- a/web-app/vitest.config.ts +++ b/web-app/vitest.config.ts @@ -38,5 +38,6 @@ export default defineConfig({ VERSION: JSON.stringify('test'), POSTHOG_KEY: JSON.stringify(''), POSTHOG_HOST: JSON.stringify(''), + AUTO_UPDATER_DISABLED: JSON.stringify('false'), }, }) From 3b349a60f1905a7efb168e497dc4680187f412ee Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 5 Aug 2025 22:32:27 +0700 Subject: [PATCH 5/5] chore: add deep_link register_all --- src-tauri/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7083cdebe..0332be3ef 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -129,6 +129,12 @@ pub fn run() { if let Err(e) = setup::install_extensions(app.handle().clone(), false) { log::error!("Failed to install extensions: {}", e); } + + #[cfg(any(windows, target_os = "linux"))] + { + use tauri_plugin_deep_link::DeepLinkExt; + app.deep_link().register_all()?; + } setup_mcp(app); Ok(()) })