From c7a1630862a63333e261dde824a5aa9331a17f9c Mon Sep 17 00:00:00 2001 From: Xiaohan-Tian Date: Thu, 14 May 2026 19:12:36 -0700 Subject: [PATCH] fix: show cache loading progress --- src/agent/llm/LocalBrowserLLMProvider.ts | 6 +--- src/util/localLLMModelCache.ts | 44 ++++++++++++++++++++---- src/util/localLLMModelManager.ts | 12 ++++--- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/agent/llm/LocalBrowserLLMProvider.ts b/src/agent/llm/LocalBrowserLLMProvider.ts index fb39812..298e5dd 100644 --- a/src/agent/llm/LocalBrowserLLMProvider.ts +++ b/src/agent/llm/LocalBrowserLLMProvider.ts @@ -66,11 +66,7 @@ export class LocalBrowserLLMProvider implements LLMProvider { LOCAL_LLM_MODEL_URL, LOCAL_LLM_MODEL_FILENAME, progress => { - if (progress.fromCache) { - LocalLLMModelManager.notifyLoadStart(true); - } else { - LocalLLMModelManager.notifyLoadProgress(progress.receivedBytes, progress.totalBytes, false); - } + LocalLLMModelManager.notifyLoadProgress(progress.receivedBytes, progress.totalBytes, progress.fromCache); }, ), ]); diff --git a/src/util/localLLMModelCache.ts b/src/util/localLLMModelCache.ts index 525411d..df71f1f 100644 --- a/src/util/localLLMModelCache.ts +++ b/src/util/localLLMModelCache.ts @@ -13,6 +13,42 @@ export interface CachedModelStreamResult { cacheWritePromise: Promise | null; } +const createProgressReader = ( + file: File, + onProgress?: (progress: ModelDownloadProgress & { fromCache: boolean }) => void, +): ReadableStreamDefaultReader => { + const sourceReader = file.stream().getReader(); + let receivedBytes = 0; + + const monitoredStream = new ReadableStream({ + async pull(controller) { + const { done, value } = await sourceReader.read(); + if (done) { + controller.close(); + return; + } + + if (!value) { + return; + } + + receivedBytes += value.byteLength; + onProgress?.({ + receivedBytes, + totalBytes: file.size, + percent: file.size > 0 ? (receivedBytes / file.size) * 100 : 0, + fromCache: true, + }); + controller.enqueue(value); + }, + async cancel(reason) { + await sourceReader.cancel(reason); + }, + }); + + return monitoredStream.getReader(); +}; + export class LocalLLMModelCache { public static async exists(filename: string = LOCAL_LLM_MODEL_FILENAME): Promise { return cache.exists(filename); @@ -41,14 +77,8 @@ export class LocalLLMModelCache { if (await this.exists(filename)) { const file = await this.getFile(filename); - onProgress?.({ - receivedBytes: file.size, - totalBytes: file.size, - percent: 100, - fromCache: true, - }); return { - reader: file.stream().getReader(), + reader: createProgressReader(file, onProgress), totalBytes: file.size, fromCache: true, cacheWritePromise: null, diff --git a/src/util/localLLMModelManager.ts b/src/util/localLLMModelManager.ts index 9d2405b..d55295f 100644 --- a/src/util/localLLMModelManager.ts +++ b/src/util/localLLMModelManager.ts @@ -116,8 +116,8 @@ export class LocalLLMModelManager { public static notifyLoadStart(fromCache: boolean): void { this.setState({ - isDownloading: !fromCache, - progressPercent: fromCache ? 100 : 0, + isDownloading: true, + progressPercent: 0, progressText: fromCache ? 'Loading local language model from browser cache...' : 'Downloading local language model...', error: '', }); @@ -127,10 +127,12 @@ export class LocalLLMModelManager { const receivedMb = (receivedBytes / (1024 * 1024)).toFixed(1); const totalMb = totalBytes ? (totalBytes / (1024 * 1024)).toFixed(1) : null; this.setState({ - isDownloading: !fromCache, + isDownloading: true, progressPercent: totalBytes ? (receivedBytes / totalBytes) * 100 : 0, progressText: fromCache - ? 'Loading local language model from browser cache...' + ? totalMb + ? `Loading local language model from browser cache... ${receivedMb} / ${totalMb} MB` + : `Loading local language model from browser cache... ${receivedMb} MB` : totalMb ? `Downloading local language model... ${receivedMb} / ${totalMb} MB` : `Downloading local language model... ${receivedMb} MB`, @@ -143,7 +145,7 @@ export class LocalLLMModelManager { isCached: true, isDownloading: false, progressPercent: 100, - progressText: 'Local language model is ready.', + progressText: 'Using the local browser model. No external API requests are being sent.', error: '', }); }