diff --git a/frontend/src/components/RunButton.tsx b/frontend/src/components/RunButton.tsx index cc9396f..8e0cd80 100644 --- a/frontend/src/components/RunButton.tsx +++ b/frontend/src/components/RunButton.tsx @@ -126,7 +126,7 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean showDownloadPrompt(t('Model file not found'), modelName); commonStore.setStatus({ status: ModelStatus.Offline }); return; - } else if (!currentModelSource?.isLocal) { + } else if (!currentModelSource?.isComplete) { showDownloadPrompt(t('Model file download is not complete'), modelName); commonStore.setStatus({ status: ModelStatus.Offline }); return; diff --git a/frontend/src/pages/Configs.tsx b/frontend/src/pages/Configs.tsx index d927ecb..5edf3a8 100644 --- a/frontend/src/pages/Configs.tsx +++ b/frontend/src/pages/Configs.tsx @@ -224,12 +224,12 @@ export const Configs: FC = observer(() => { modelName: data.value }); }}> - {!commonStore.modelSourceList.find(item => item.name === selectedConfig.modelParameters.modelName)?.isLocal + {!commonStore.modelSourceList.find(item => item.name === selectedConfig.modelParameters.modelName)?.isComplete && } {commonStore.modelSourceList.map((modelItem, index) => - modelItem.isLocal && + modelItem.isComplete && )} } onClick={() => { diff --git a/frontend/src/pages/Downloads.tsx b/frontend/src/pages/Downloads.tsx index 3021a01..80991a0 100644 --- a/frontend/src/pages/Downloads.tsx +++ b/frontend/src/pages/Downloads.tsx @@ -7,7 +7,7 @@ import { Divider, Field, ProgressBar } from '@fluentui/react-components'; import { bytesToGb, bytesToKb, bytesToMb, refreshLocalModels } from '../utils'; import { ToolTipButton } from '../components/ToolTipButton'; import { Folder20Regular, Pause20Regular, Play20Regular } from '@fluentui/react-icons'; -import { ContinueDownload, OpenFileFolder, PauseDownload } from '../../wailsjs/go/backend_golang/App'; +import { AddToDownloadList, OpenFileFolder, PauseDownload } from '../../wailsjs/go/backend_golang/App'; export type DownloadStatus = { name: string; @@ -30,10 +30,27 @@ export const Downloads: FC = observer(() => { console.log('finishedModelsLen:', finishedModelsLen); }, [finishedModelsLen]); + let displayList = commonStore.downloadList.slice(); + const downloadListNames = displayList.map(s => s.name); + commonStore.lastUnfinishedModelDownloads.forEach((status) => { + const unfinishedIndex = downloadListNames.indexOf(status.name); + if (unfinishedIndex === -1) { + displayList.push(status); + } else { + const unfinishedStatus = displayList[unfinishedIndex]; + if (unfinishedStatus.transferred < status.transferred) { + status.downloading = unfinishedStatus.downloading; + delete displayList[unfinishedIndex]; + displayList.push(status); + } + } + }); + displayList = displayList.reverse(); + return ( - {commonStore.downloadList.slice().reverse().map((status, index) => { + {displayList.map((status, index) => { const downloadProgress = `${status.progress.toFixed(2)}%`; const downloadSpeed = `${status.downloading ? bytesToMb(status.speed) : '0'}MB/s`; let downloadDetails: string; @@ -59,7 +76,7 @@ export const Downloads: FC = observer(() => { if (status.downloading) PauseDownload(status.url); else - ContinueDownload(status.url); + AddToDownloadList(status.path, status.url); }} />} } onClick={() => { OpenFileFolder(status.path, false); diff --git a/frontend/src/pages/Models.tsx b/frontend/src/pages/Models.tsx index f9d3856..b4c394e 100644 --- a/frontend/src/pages/Models.tsx +++ b/frontend/src/pages/Models.tsx @@ -31,7 +31,9 @@ export type ModelSourceItem = { SHA256?: string; url?: string; downloadUrl?: string; + isComplete?: boolean; isLocal?: boolean; + localSize?: number; lastUpdatedMs?: number; hide?: boolean; }; @@ -125,7 +127,7 @@ const columns: TableColumnDefinition[] = [ createTableColumn({ columnId: 'actions', compare: (a, b) => { - return a.isLocal ? -1 : 1; + return a.isComplete ? -1 : 1; }, renderHeaderCell: () => { const { t } = useTranslation(); @@ -140,12 +142,12 @@ const columns: TableColumnDefinition[] = [
{ - item.isLocal && + item.isComplete && } onClick={() => { OpenFileFolder(`${commonStore.settings.customModelsPath}/${item.name}`, true); }} /> } - {item.downloadUrl && !item.isLocal && + {item.downloadUrl && !item.isComplete && } onClick={() => { toastWithButton(`${t('Downloading')} ${item.name}`, t('Check'), () => { navigate({ pathname: '/downloads' }); @@ -203,7 +205,7 @@ export const Models: FC = observer(() => {
> {({ item, rowId }) => ( - (!item.hide || item.isLocal) && + (!item.hide || item.isComplete) && key={rowId}> {({ renderCell }) => ( {renderCell(item)} diff --git a/frontend/src/startup.ts b/frontend/src/startup.ts index 12657d3..dadca46 100644 --- a/frontend/src/startup.ts +++ b/frontend/src/startup.ts @@ -16,7 +16,7 @@ export async function startup() { await GetPlatform().then(p => commonStore.setPlatform(p as Platform)); await initConfig(); - initCache().then(initRemoteText); // depends on config customModelsPath + initCache(true).then(initRemoteText); // depends on config customModelsPath if (commonStore.settings.autoUpdatesCheck) // depends on config settings checkUpdate(); @@ -58,11 +58,11 @@ async function initConfig() { }); } -async function initCache() { +async function initCache(initUnfinishedModels: boolean) { await ReadJson('cache.json').then((cacheData: Cache) => { if (cacheData.depComplete) commonStore.setDepComplete(cacheData.depComplete); }).catch(() => { }); - await refreshModels(false); + await refreshModels(false, initUnfinishedModels); } \ No newline at end of file diff --git a/frontend/src/stores/commonStore.ts b/frontend/src/stores/commonStore.ts index f093ac5..398f7ea 100644 --- a/frontend/src/stores/commonStore.ts +++ b/frontend/src/stores/commonStore.ts @@ -55,6 +55,7 @@ class CommonStore { modelSourceList: ModelSourceItem[] = []; // downloads downloadList: DownloadStatus[] = []; + lastUnfinishedModelDownloads: DownloadStatus[] = []; // settings advancedCollapsed: boolean = true; settings: SettingsType = { @@ -197,6 +198,10 @@ class CommonStore { setAdvancedCollapsed(value: boolean) { this.advancedCollapsed = value; } + + setLastUnfinishedModelDownloads(value: DownloadStatus[]) { + this.lastUnfinishedModelDownloads = value; + } } export default new CommonStore(); \ No newline at end of file diff --git a/frontend/src/utils/index.tsx b/frontend/src/utils/index.tsx index 1b9e2ad..2ac09af 100644 --- a/frontend/src/utils/index.tsx +++ b/frontend/src/utils/index.tsx @@ -16,6 +16,7 @@ import { Button } from '@fluentui/react-components'; import { Language, Languages, SettingsType } from '../pages/Settings'; import { ModelSourceItem } from '../pages/Models'; import { ModelConfig, ModelParameters } from '../pages/Configs'; +import { DownloadStatus } from '../pages/Downloads'; export type Cache = { models: ModelSourceItem[] @@ -47,9 +48,11 @@ export async function refreshBuiltInModels(readCache: boolean = false) { return cache; } -export async function refreshLocalModels(cache: { models: ModelSourceItem[] }, filter: boolean = true) { +export async function refreshLocalModels(cache: { + models: ModelSourceItem[] +}, filter: boolean = true, initUnfinishedModels: boolean = false) { if (filter) - cache.models = cache.models.filter(m => !m.isLocal); //TODO BUG cause local but in manifest files to be removed, so currently cache is disabled + cache.models = cache.models.filter(m => !m.isComplete); //TODO BUG cause local but in manifest files to be removed, so currently cache is disabled await ListDirFiles(commonStore.settings.customModelsPath).then((data) => { cache.models.push(...data.flatMap(d => { @@ -58,8 +61,9 @@ export async function refreshLocalModels(cache: { models: ModelSourceItem[] }, f name: d.name, size: d.size, lastUpdated: d.modTime, + isComplete: true, isLocal: true - }]; + }] as ModelSourceItem[]; return []; })); }).catch(() => { @@ -80,17 +84,43 @@ export async function refreshLocalModels(cache: { models: ModelSourceItem[] }, f } else { cache.models[i] = Object.assign({}, cache.models[j], cache.models[i]); } - } // else is bad local file + } // else is not complete local file + cache.models[i].isLocal = true; + cache.models[i].localSize = cache.models[j].size; cache.models.splice(j, 1); j--; } } } commonStore.setModelSourceList(cache.models); + if (initUnfinishedModels) + initLastUnfinishedModelDownloads(); await saveCache().catch(() => { }); } +function initLastUnfinishedModelDownloads() { + const list: DownloadStatus[] = []; + commonStore.modelSourceList.forEach((item) => { + if (item.isLocal && !item.isComplete) { + list.push( + { + name: item.name, + path: `${commonStore.settings.customModelsPath}/${item.name}`, + url: item.downloadUrl!, + transferred: item.localSize!, + size: item.size, + speed: 0, + progress: item.localSize! / item.size * 100, + downloading: false, + done: false + } + ); + } + }); + commonStore.setLastUnfinishedModelDownloads(list); +} + export async function refreshRemoteModels(cache: { models: ModelSourceItem[] }) { const manifestUrls = commonStore.modelSourceManifestList.split(/[,,;;\n]/); const requests = manifestUrls.filter(url => url.endsWith('.json')).map( @@ -116,9 +146,9 @@ export async function refreshRemoteModels(cache: { models: ModelSourceItem[] }) }); } -export const refreshModels = async (readCache: boolean = false) => { +export const refreshModels = async (readCache: boolean = false, initUnfinishedModels: boolean = false) => { const cache = await refreshBuiltInModels(readCache); - await refreshLocalModels(cache); + await refreshLocalModels(cache, false, initUnfinishedModels); await refreshRemoteModels(cache); };