From c355649759bc87398127e185a4a6659195eb83a2 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 11 Aug 2025 15:29:50 +0700 Subject: [PATCH 01/11] fix: HF token is not used while searching repositories --- web-app/src/routes/hub/index.tsx | 4 +++- web-app/src/services/models.ts | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/web-app/src/routes/hub/index.tsx b/web-app/src/routes/hub/index.tsx index 08e8602ba..2a6e6220d 100644 --- a/web-app/src/routes/hub/index.tsx +++ b/web-app/src/routes/hub/index.tsx @@ -39,6 +39,7 @@ import HeaderPage from '@/containers/HeaderPage' import { Loader } from 'lucide-react' import { useTranslation } from '@/i18n/react-i18next-compat' import Fuse from 'fuse.js' +import { useGeneralSetting } from '@/hooks/useGeneralSetting' type ModelProps = { model: CatalogModel @@ -57,6 +58,7 @@ export const Route = createFileRoute(route.hub.index as any)({ function Hub() { const parentRef = useRef(null) + const { huggingfaceToken } = useGeneralSetting() const { t } = useTranslation() const sortOptions = [ @@ -185,7 +187,7 @@ function Hub() { addModelSourceTimeoutRef.current = setTimeout(async () => { try { // Fetch HuggingFace repository information - const repoInfo = await fetchHuggingFaceRepo(e.target.value) + const repoInfo = await fetchHuggingFaceRepo(e.target.value, huggingfaceToken) if (repoInfo) { const catalogModel = convertHfRepoToCatalogModel(repoInfo) if ( diff --git a/web-app/src/services/models.ts b/web-app/src/services/models.ts index 12bf1997d..50b1c0ebc 100644 --- a/web-app/src/services/models.ts +++ b/web-app/src/services/models.ts @@ -99,7 +99,8 @@ export const fetchModelCatalog = async (): Promise => { * @returns A promise that resolves to the repository information. */ export const fetchHuggingFaceRepo = async ( - repoId: string + repoId: string, + hfToken?: string ): Promise => { try { // Clean the repo ID to handle various input formats @@ -114,7 +115,14 @@ export const fetchHuggingFaceRepo = async ( } const response = await fetch( - `https://huggingface.co/api/models/${cleanRepoId}?blobs=true` + `https://huggingface.co/api/models/${cleanRepoId}?blobs=true`, + { + headers: hfToken + ? { + Authorization: `Bearer ${hfToken}`, + } + : {}, + } ) if (!response.ok) { From 15ba8d217f6584fddf40df0a08c92b1f84bf31d7 Mon Sep 17 00:00:00 2001 From: Nicole Zhu Date: Mon, 11 Aug 2025 17:39:37 +0800 Subject: [PATCH 02/11] docs: Update 3-epic.md --- .github/ISSUE_TEMPLATE/3-epic.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/3-epic.md b/.github/ISSUE_TEMPLATE/3-epic.md index afffc6b5c..3e05d8dd6 100644 --- a/.github/ISSUE_TEMPLATE/3-epic.md +++ b/.github/ISSUE_TEMPLATE/3-epic.md @@ -1,12 +1,27 @@ --- name: 🌟 Epic -about: Major building block that advances Jan's goals +about: User stories and specs title: 'epic: ' type: Epic --- -## Goal +## User Stories -## Tasklist +- As a [user type], I can [do something] so that [outcome] -## Out of scope +## Not in scope + +- + +## User Flows & Designs + +- Key user flows +- Figma link +- Edge cases +- Error states + +## Engineering Decisions + +- **Technical Approach:** Brief outline of the solution. +- **Key Trade-offs:** What’s been considered/rejected and why. +- **Dependencies:** APIs, services, libraries, teams. From 376873b5ee2659a445549e8a54ad7f3a82b982ca Mon Sep 17 00:00:00 2001 From: Nicole Zhu Date: Mon, 11 Aug 2025 17:40:35 +0800 Subject: [PATCH 03/11] docs: Update 4-goal.md --- .github/ISSUE_TEMPLATE/4-goal.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/4-goal.md b/.github/ISSUE_TEMPLATE/4-goal.md index 28b32382a..8d649c281 100644 --- a/.github/ISSUE_TEMPLATE/4-goal.md +++ b/.github/ISSUE_TEMPLATE/4-goal.md @@ -1,13 +1,38 @@ --- name: 🎯 Goal -about: External communication of Jan's roadmap and objectives +about: Roadmap goals for our users title: 'goal: ' type: Goal --- ## Goal -## Tasklist +> Why are we doing this? 1 liner value proposition -## Out of scope +_e.g. Make onboarding to Jan 3x easier_ +## Success Criteria + +> When do we consider this done? Limit to 3. + +1. _e.g. Redesign onboarding flow to remove redundant steps._ +2. _e.g. Add a “getting started” guide_ +3. _e.g. Make local model setup more “normie” friendly_ + +## Non Goals + +> What is out of scope? + +- _e.g. Take advanced users through customizing settings_ + +## User research (if any) + +> Links to user messages and interviews + +## Design inspo + +> Links + +## Open questions + +> What are we not sure about? From d5ca195059f6905e7ad09d027a15d2b96cba1623 Mon Sep 17 00:00:00 2001 From: Minh141120 Date: Mon, 11 Aug 2025 17:10:51 +0700 Subject: [PATCH 04/11] ci: add path for tauri nightly build --- .github/workflows/jan-tauri-build-nightly.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/jan-tauri-build-nightly.yaml b/.github/workflows/jan-tauri-build-nightly.yaml index 106174eff..32bba083a 100644 --- a/.github/workflows/jan-tauri-build-nightly.yaml +++ b/.github/workflows/jan-tauri-build-nightly.yaml @@ -16,6 +16,23 @@ on: branches: - release/** - dev + paths: + - '.github/workflows/jan-tauri-build-nightly.yaml' + - '.github/workflows/template-get-update-version.yml' + - '.github/workflows/template-tauri-build-macos.yml' + - '.github/workflows/template-tauri-build-windows-x64.yml' + - '.github/workflows/template-tauri-build-linux-x64.yml' + - '.github/workflows/template-noti-discord-and-update-url-readme.yml' + - 'src-tauri/**' + - 'core/**' + - 'web-app/**' + - 'extensions/**' + - 'scripts/**' + - 'pre-install/**' + - 'Makefile' + - 'package.json' + - 'mise.toml' + jobs: set-public-provider: From 9ed98614fee32a7dd98635493b36305e3d49c101 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 11 Aug 2025 19:42:59 +0700 Subject: [PATCH 05/11] fix: factory reset process got blocked --- src-tauri/src/core/cmd.rs | 5 +++-- src-tauri/src/core/mcp.rs | 20 ++++++++++++++++++++ src-tauri/src/core/setup.rs | 24 ------------------------ src-tauri/src/lib.rs | 5 ++++- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src-tauri/src/core/cmd.rs b/src-tauri/src/core/cmd.rs index 37937cfe0..dbfb41fc3 100644 --- a/src-tauri/src/core/cmd.rs +++ b/src-tauri/src/core/cmd.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::{fs, io, path::PathBuf}; use tauri::{AppHandle, Manager, Runtime, State}; -use crate::core::utils::extensions::inference_llamacpp_extension::cleanup::cleanup_processes; +use crate::core::{mcp::clean_up_mcp_servers, utils::extensions::inference_llamacpp_extension::cleanup::cleanup_processes}; use super::{server, setup, state::AppState}; @@ -125,6 +125,7 @@ pub fn factory_reset(app_handle: tauri::AppHandle, state: State<'_, AppState>) { log::info!("Factory reset, removing data folder: {:?}", data_folder); tauri::async_runtime::block_on(async { + clean_up_mcp_servers(state.clone()).await; cleanup_processes(state).await; if data_folder.exists() { @@ -138,7 +139,7 @@ pub fn factory_reset(app_handle: tauri::AppHandle, state: State<'_, AppState>) { let _ = fs::create_dir_all(&data_folder).map_err(|e| e.to_string()); // Reset the configuration - let mut default_config = AppConfiguration::default(); + let mut default_config: AppConfiguration = AppConfiguration::default(); default_config.data_folder = default_data_folder_path(app_handle.clone()); let _ = update_app_configuration(app_handle.clone(), default_config); diff --git a/src-tauri/src/core/mcp.rs b/src-tauri/src/core/mcp.rs index 7485fd17e..93497772c 100644 --- a/src-tauri/src/core/mcp.rs +++ b/src-tauri/src/core/mcp.rs @@ -751,6 +751,26 @@ pub async fn reset_mcp_restart_count(state: State<'_, AppState>, server_name: St Ok(()) } +pub async fn clean_up_mcp_servers( + state: State<'_, AppState>, +) { + log::info!("Cleaning up MCP servers"); + + // Stop all running MCP servers + let _ = stop_mcp_servers(state.mcp_servers.clone()).await; + + // Clear active servers and restart counts + { + let mut active_servers = state.mcp_active_servers.lock().await; + active_servers.clear(); + } + { + let mut restart_counts = state.mcp_restart_counts.lock().await; + restart_counts.clear(); + } + log::info!("MCP servers cleaned up successfully"); +} + pub async fn stop_mcp_servers( servers_state: Arc>>>, ) -> Result<(), String> { diff --git a/src-tauri/src/core/setup.rs b/src-tauri/src/core/setup.rs index 940122235..0f32f4c7e 100644 --- a/src-tauri/src/core/setup.rs +++ b/src-tauri/src/core/setup.rs @@ -199,30 +199,6 @@ pub fn setup_mcp(app: &App) { let state = app.state::(); let servers = state.mcp_servers.clone(); let app_handle: tauri::AppHandle = app.handle().clone(); - // Setup kill-mcp-servers event listener (similar to kill-sidecar) - let app_handle_for_kill = app_handle.clone(); - app_handle.listen("kill-mcp-servers", move |_event| { - let app_handle = app_handle_for_kill.clone(); - tauri::async_runtime::spawn(async move { - log::info!("Received kill-mcp-servers event - cleaning up MCP servers"); - let app_state = app_handle.state::(); - // Stop all running MCP servers - if let Err(e) = super::mcp::stop_mcp_servers(app_state.mcp_servers.clone()).await { - log::error!("Failed to stop MCP servers: {}", e); - return; - } - // Clear active servers and restart counts - { - let mut active_servers = app_state.mcp_active_servers.lock().await; - active_servers.clear(); - } - { - let mut restart_counts = app_state.mcp_restart_counts.lock().await; - restart_counts.clear(); - } - log::info!("MCP servers cleaned up successfully"); - }); - }); tauri::async_runtime::spawn(async move { if let Err(e) = run_mcp_commands(&app_handle, servers).await { log::error!("Failed to run mcp commands: {}", e); diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e449fc739..b0935206b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -10,6 +10,8 @@ use std::{collections::HashMap, sync::Arc}; use tauri::{Emitter, Manager, RunEvent}; use tokio::sync::Mutex; +use crate::core::mcp::clean_up_mcp_servers; + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let mut builder = tauri::Builder::default(); @@ -143,10 +145,10 @@ pub fn run() { .on_window_event(|window, event| match event { tauri::WindowEvent::CloseRequested { .. } => { if window.label() == "main" { - window.emit("kill-mcp-servers", ()).unwrap(); let state = window.app_handle().state::(); tauri::async_runtime::block_on(async { + clean_up_mcp_servers(state.clone()).await; cleanup_processes(state).await; }); } @@ -173,6 +175,7 @@ pub fn run() { } // Quick cleanup with shorter timeout + clean_up_mcp_servers(state.clone()).await; cleanup_processes(state).await; }); }); From 25a0c14be8bdc201bcfcd7f29f17b6c278e03424 Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 12 Aug 2025 10:33:41 +0700 Subject: [PATCH 06/11] chore: whitelist jan model with tool use support by default --- web-app/src/types/models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-app/src/types/models.ts b/web-app/src/types/models.ts index e2319a4e3..fec96aa1c 100644 --- a/web-app/src/types/models.ts +++ b/web-app/src/types/models.ts @@ -17,7 +17,7 @@ export enum ModelCapabilities { // TODO: Remove this enum when we integrate llama.cpp extension export enum DefaultToolUseSupportedModels { - JanNano = 'jan-nano', + JanNano = 'jan-', Qwen3 = 'qwen3', Lucy = 'lucy', } From 276a286853cf2ff7202b95172328a2bc681b1149 Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 12 Aug 2025 10:37:00 +0700 Subject: [PATCH 07/11] fix: tests --- web-app/src/services/__tests__/models.test.ts | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/web-app/src/services/__tests__/models.test.ts b/web-app/src/services/__tests__/models.test.ts index b648b2677..b783f6ab5 100644 --- a/web-app/src/services/__tests__/models.test.ts +++ b/web-app/src/services/__tests__/models.test.ts @@ -325,7 +325,10 @@ describe('models service', () => { expect(result).toEqual(mockRepoData) expect(fetch).toHaveBeenCalledWith( - 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true' + 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true', + { + headers: {}, + } ) }) @@ -341,19 +344,28 @@ describe('models service', () => { 'https://huggingface.co/microsoft/DialoGPT-medium' ) expect(fetch).toHaveBeenCalledWith( - 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true' + 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true', + { + headers: {}, + } ) // Test with domain prefix await fetchHuggingFaceRepo('huggingface.co/microsoft/DialoGPT-medium') expect(fetch).toHaveBeenCalledWith( - 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true' + 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true', + { + headers: {}, + } ) // Test with trailing slash await fetchHuggingFaceRepo('microsoft/DialoGPT-medium/') expect(fetch).toHaveBeenCalledWith( - 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true' + 'https://huggingface.co/api/models/microsoft/DialoGPT-medium?blobs=true', + { + headers: {}, + } ) }) @@ -379,7 +391,10 @@ describe('models service', () => { expect(result).toBeNull() expect(fetch).toHaveBeenCalledWith( - 'https://huggingface.co/api/models/nonexistent/model?blobs=true' + 'https://huggingface.co/api/models/nonexistent/model?blobs=true', + { + headers: {}, + } ) }) From 736790473eec796accaa400e22cb717b77aa6c54 Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 12 Aug 2025 11:17:00 +0700 Subject: [PATCH 08/11] fix: duplicate model while searching --- web-app/src/hooks/useModelSources.ts | 10 ---------- web-app/src/routes/hub/index.tsx | 8 +++++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/web-app/src/hooks/useModelSources.ts b/web-app/src/hooks/useModelSources.ts index 916d8eae2..72d011582 100644 --- a/web-app/src/hooks/useModelSources.ts +++ b/web-app/src/hooks/useModelSources.ts @@ -8,7 +8,6 @@ type ModelSourcesState = { sources: CatalogModel[] error: Error | null loading: boolean - addSource: (source: CatalogModel) => void fetchSources: () => Promise } @@ -18,15 +17,6 @@ export const useModelSources = create()( sources: [], error: null, loading: false, - - addSource: (source: CatalogModel) => { - set((state) => ({ - sources: [ - ...state.sources.filter((e) => e.model_name !== source.model_name), - source, - ], - })) - }, fetchSources: async () => { set({ loading: true, error: null }) try { diff --git a/web-app/src/routes/hub/index.tsx b/web-app/src/routes/hub/index.tsx index 2a6e6220d..5e43cc70e 100644 --- a/web-app/src/routes/hub/index.tsx +++ b/web-app/src/routes/hub/index.tsx @@ -73,7 +73,7 @@ function Hub() { } }, []) - const { sources, addSource, fetchSources, loading } = useModelSources() + const { sources, fetchSources, loading } = useModelSources() const [searchValue, setSearchValue] = useState('') const [sortSelected, setSortSelected] = useState('newest') @@ -187,14 +187,16 @@ function Hub() { addModelSourceTimeoutRef.current = setTimeout(async () => { try { // Fetch HuggingFace repository information - const repoInfo = await fetchHuggingFaceRepo(e.target.value, huggingfaceToken) + const repoInfo = await fetchHuggingFaceRepo( + e.target.value, + huggingfaceToken + ) if (repoInfo) { const catalogModel = convertHfRepoToCatalogModel(repoInfo) if ( !sources.some((s) => s.model_name === catalogModel.model_name) ) { setHuggingFaceRepo(catalogModel) - addSource(catalogModel) } } } catch (error) { From 8e5fac83fdcf4a0d9b9b96a30e90ec82e28c550e Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 12 Aug 2025 11:25:47 +0700 Subject: [PATCH 09/11] fix: deprecate addSource tests since the function was removed --- .../hooks/__tests__/useModelSources.test.ts | 148 ------------------ 1 file changed, 148 deletions(-) diff --git a/web-app/src/hooks/__tests__/useModelSources.test.ts b/web-app/src/hooks/__tests__/useModelSources.test.ts index a14a8107f..d010f7f8a 100644 --- a/web-app/src/hooks/__tests__/useModelSources.test.ts +++ b/web-app/src/hooks/__tests__/useModelSources.test.ts @@ -49,7 +49,6 @@ describe('useModelSources', () => { expect(result.current.error).toBe(null) expect(result.current.loading).toBe(false) expect(typeof result.current.fetchSources).toBe('function') - expect(typeof result.current.addSource).toBe('function') }) describe('fetchSources', () => { @@ -225,153 +224,6 @@ describe('useModelSources', () => { }) }) - describe('addSource', () => { - it('should add a new source to the store', () => { - const { result } = renderHook(() => useModelSources()) - - const testModel: CatalogModel = { - model_name: 'test-model', - description: 'Test model description', - developer: 'test-developer', - downloads: 100, - num_quants: 2, - quants: [ - { - model_id: 'test-model-q4', - path: 'https://example.com/test-model-q4.gguf', - file_size: '2.0 GB', - }, - ], - created_at: '2023-01-01T00:00:00Z', - } - - act(() => { - result.current.addSource(testModel) - }) - - expect(result.current.sources).toHaveLength(1) - expect(result.current.sources[0]).toEqual(testModel) - }) - - it('should replace existing source with same model_name', () => { - const { result } = renderHook(() => useModelSources()) - - const originalModel: CatalogModel = { - model_name: 'duplicate-model', - description: 'Original description', - developer: 'original-developer', - downloads: 50, - num_quants: 1, - quants: [], - created_at: '2023-01-01T00:00:00Z', - } - - const updatedModel: CatalogModel = { - model_name: 'duplicate-model', - description: 'Updated description', - developer: 'updated-developer', - downloads: 150, - num_quants: 2, - quants: [ - { - model_id: 'duplicate-model-q4', - path: 'https://example.com/duplicate-model-q4.gguf', - file_size: '3.0 GB', - }, - ], - created_at: '2023-02-01T00:00:00Z', - } - - act(() => { - result.current.addSource(originalModel) - }) - - expect(result.current.sources).toHaveLength(1) - - act(() => { - result.current.addSource(updatedModel) - }) - - expect(result.current.sources).toHaveLength(1) - expect(result.current.sources[0]).toEqual(updatedModel) - }) - - it('should handle multiple different sources', () => { - const { result } = renderHook(() => useModelSources()) - - const model1: CatalogModel = { - model_name: 'model-1', - description: 'First model', - developer: 'developer-1', - downloads: 100, - num_quants: 1, - quants: [], - created_at: '2023-01-01T00:00:00Z', - } - - const model2: CatalogModel = { - model_name: 'model-2', - description: 'Second model', - developer: 'developer-2', - downloads: 200, - num_quants: 1, - quants: [], - created_at: '2023-01-02T00:00:00Z', - } - - act(() => { - result.current.addSource(model1) - }) - - act(() => { - result.current.addSource(model2) - }) - - expect(result.current.sources).toHaveLength(2) - expect(result.current.sources).toContainEqual(model1) - expect(result.current.sources).toContainEqual(model2) - }) - - it('should handle CatalogModel with complete quants data', () => { - const { result } = renderHook(() => useModelSources()) - - const modelWithQuants: CatalogModel = { - model_name: 'model-with-quants', - description: 'Model with quantizations', - developer: 'quant-developer', - downloads: 500, - num_quants: 3, - quants: [ - { - model_id: 'model-q4_k_m', - path: 'https://example.com/model-q4_k_m.gguf', - file_size: '2.0 GB', - }, - { - model_id: 'model-q8_0', - path: 'https://example.com/model-q8_0.gguf', - file_size: '4.0 GB', - }, - { - model_id: 'model-f16', - path: 'https://example.com/model-f16.gguf', - file_size: '8.0 GB', - }, - ], - created_at: '2023-01-01T00:00:00Z', - readme: 'https://example.com/readme.md', - } - - act(() => { - result.current.addSource(modelWithQuants) - }) - - expect(result.current.sources).toHaveLength(1) - expect(result.current.sources[0]).toEqual(modelWithQuants) - expect(result.current.sources[0].quants).toHaveLength(3) - }) - }) - describe('state management', () => { it('should maintain state across multiple hook instances', () => { const { result: result1 } = renderHook(() => useModelSources()) From 1a72a592b98343a8036beef630a7b36889fdf53b Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 12 Aug 2025 12:07:25 +0700 Subject: [PATCH 10/11] fix: visualize readme content for private repo with HF token --- web-app/src/routes/hub/$modelId.tsx | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/web-app/src/routes/hub/$modelId.tsx b/web-app/src/routes/hub/$modelId.tsx index d46c20ca2..228ebd0e6 100644 --- a/web-app/src/routes/hub/$modelId.tsx +++ b/web-app/src/routes/hub/$modelId.tsx @@ -27,6 +27,7 @@ import { import { Progress } from '@/components/ui/progress' import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' +import { useGeneralSetting } from '@/hooks/useGeneralSetting' type SearchParams = { repo: string @@ -42,6 +43,7 @@ export const Route = createFileRoute('/hub/$modelId')({ function HubModelDetail() { const { modelId } = useParams({ from: Route.id }) const navigate = useNavigate() + const { huggingfaceToken } = useGeneralSetting() const { sources, fetchSources } = useModelSources() // eslint-disable-next-line @typescript-eslint/no-explicit-any const search = useSearch({ from: Route.id as any }) @@ -60,12 +62,15 @@ function HubModelDetail() { }, [fetchSources]) const fetchRepo = useCallback(async () => { - const repoInfo = await fetchHuggingFaceRepo(search.repo || modelId) + const repoInfo = await fetchHuggingFaceRepo( + search.repo || modelId, + huggingfaceToken + ) if (repoInfo) { const repoDetail = convertHfRepoToCatalogModel(repoInfo) setRepoData(repoDetail) } - }, [modelId, search]) + }, [modelId, search, huggingfaceToken]) useEffect(() => { fetchRepo() @@ -151,7 +156,13 @@ function HubModelDetail() { useEffect(() => { if (modelData?.readme) { setIsLoadingReadme(true) - fetch(modelData.readme) + fetch(modelData.readme, { + headers: huggingfaceToken + ? { + Authorization: `Bearer ${huggingfaceToken}`, + } + : {}, + }) .then((response) => response.text()) .then((content) => { setReadmeContent(content) @@ -162,7 +173,7 @@ function HubModelDetail() { setIsLoadingReadme(false) }) } - }, [modelData?.readme]) + }, [modelData?.readme, huggingfaceToken]) if (!modelData) { return ( From 7b2d0432e6ec7cf4bd14aa1fb1fb326df874e357 Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 12 Aug 2025 12:46:26 +0700 Subject: [PATCH 11/11] fix: weird HF readme accessibility --- web-app/src/routes/hub/$modelId.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/web-app/src/routes/hub/$modelId.tsx b/web-app/src/routes/hub/$modelId.tsx index 228ebd0e6..f34057ae4 100644 --- a/web-app/src/routes/hub/$modelId.tsx +++ b/web-app/src/routes/hub/$modelId.tsx @@ -156,13 +156,20 @@ function HubModelDetail() { useEffect(() => { if (modelData?.readme) { setIsLoadingReadme(true) - fetch(modelData.readme, { - headers: huggingfaceToken - ? { - Authorization: `Bearer ${huggingfaceToken}`, - } - : {}, - }) + // Try fetching without headers first + // There is a weird issue where this HF link will return error when access public repo with auth header + fetch(modelData.readme) + .then((response) => { + if (!response.ok && huggingfaceToken && modelData?.readme) { + // Retry with Authorization header if first fetch failed + return fetch(modelData.readme, { + headers: { + Authorization: `Bearer ${huggingfaceToken}`, + }, + }) + } + return response + }) .then((response) => response.text()) .then((content) => { setReadmeContent(content)