display models that have not been fully downloaded in Downloads page, even if the program is restarted

This commit is contained in:
josc146 2023-06-23 16:03:57 +08:00
parent 447f4572b1
commit 97f6af595e
7 changed files with 73 additions and 19 deletions

View File

@ -126,7 +126,7 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
showDownloadPrompt(t('Model file not found'), modelName); showDownloadPrompt(t('Model file not found'), modelName);
commonStore.setStatus({ status: ModelStatus.Offline }); commonStore.setStatus({ status: ModelStatus.Offline });
return; return;
} else if (!currentModelSource?.isLocal) { } else if (!currentModelSource?.isComplete) {
showDownloadPrompt(t('Model file download is not complete'), modelName); showDownloadPrompt(t('Model file download is not complete'), modelName);
commonStore.setStatus({ status: ModelStatus.Offline }); commonStore.setStatus({ status: ModelStatus.Offline });
return; return;

View File

@ -224,12 +224,12 @@ export const Configs: FC = observer(() => {
modelName: data.value modelName: data.value
}); });
}}> }}>
{!commonStore.modelSourceList.find(item => item.name === selectedConfig.modelParameters.modelName)?.isLocal {!commonStore.modelSourceList.find(item => item.name === selectedConfig.modelParameters.modelName)?.isComplete
&& <option key={-1} && <option key={-1}
value={selectedConfig.modelParameters.modelName}>{selectedConfig.modelParameters.modelName} value={selectedConfig.modelParameters.modelName}>{selectedConfig.modelParameters.modelName}
</option>} </option>}
{commonStore.modelSourceList.map((modelItem, index) => {commonStore.modelSourceList.map((modelItem, index) =>
modelItem.isLocal && <option key={index} value={modelItem.name}>{modelItem.name}</option> modelItem.isComplete && <option key={index} value={modelItem.name}>{modelItem.name}</option>
)} )}
</Select> </Select>
<ToolTipButton desc={t('Manage Models')} icon={<DataUsageSettings20Regular />} onClick={() => { <ToolTipButton desc={t('Manage Models')} icon={<DataUsageSettings20Regular />} onClick={() => {

View File

@ -7,7 +7,7 @@ import { Divider, Field, ProgressBar } from '@fluentui/react-components';
import { bytesToGb, bytesToKb, bytesToMb, refreshLocalModels } from '../utils'; import { bytesToGb, bytesToKb, bytesToMb, refreshLocalModels } from '../utils';
import { ToolTipButton } from '../components/ToolTipButton'; import { ToolTipButton } from '../components/ToolTipButton';
import { Folder20Regular, Pause20Regular, Play20Regular } from '@fluentui/react-icons'; 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 = { export type DownloadStatus = {
name: string; name: string;
@ -30,10 +30,27 @@ export const Downloads: FC = observer(() => {
console.log('finishedModelsLen:', finishedModelsLen); console.log('finishedModelsLen:', 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 ( return (
<Page title={t('Downloads')} content={ <Page title={t('Downloads')} content={
<div className="flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-1"> <div className="flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-1">
{commonStore.downloadList.slice().reverse().map((status, index) => { {displayList.map((status, index) => {
const downloadProgress = `${status.progress.toFixed(2)}%`; const downloadProgress = `${status.progress.toFixed(2)}%`;
const downloadSpeed = `${status.downloading ? bytesToMb(status.speed) : '0'}MB/s`; const downloadSpeed = `${status.downloading ? bytesToMb(status.speed) : '0'}MB/s`;
let downloadDetails: string; let downloadDetails: string;
@ -59,7 +76,7 @@ export const Downloads: FC = observer(() => {
if (status.downloading) if (status.downloading)
PauseDownload(status.url); PauseDownload(status.url);
else else
ContinueDownload(status.url); AddToDownloadList(status.path, status.url);
}} />} }} />}
<ToolTipButton desc={t('Open Folder')} icon={<Folder20Regular />} onClick={() => { <ToolTipButton desc={t('Open Folder')} icon={<Folder20Regular />} onClick={() => {
OpenFileFolder(status.path, false); OpenFileFolder(status.path, false);

View File

@ -31,7 +31,9 @@ export type ModelSourceItem = {
SHA256?: string; SHA256?: string;
url?: string; url?: string;
downloadUrl?: string; downloadUrl?: string;
isComplete?: boolean;
isLocal?: boolean; isLocal?: boolean;
localSize?: number;
lastUpdatedMs?: number; lastUpdatedMs?: number;
hide?: boolean; hide?: boolean;
}; };
@ -125,7 +127,7 @@ const columns: TableColumnDefinition<ModelSourceItem>[] = [
createTableColumn<ModelSourceItem>({ createTableColumn<ModelSourceItem>({
columnId: 'actions', columnId: 'actions',
compare: (a, b) => { compare: (a, b) => {
return a.isLocal ? -1 : 1; return a.isComplete ? -1 : 1;
}, },
renderHeaderCell: () => { renderHeaderCell: () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -140,12 +142,12 @@ const columns: TableColumnDefinition<ModelSourceItem>[] = [
<TableCellLayout> <TableCellLayout>
<div className="flex gap-1"> <div className="flex gap-1">
{ {
item.isLocal && item.isComplete &&
<ToolTipButton desc={t('Open Folder')} icon={<Folder20Regular />} onClick={() => { <ToolTipButton desc={t('Open Folder')} icon={<Folder20Regular />} onClick={() => {
OpenFileFolder(`${commonStore.settings.customModelsPath}/${item.name}`, true); OpenFileFolder(`${commonStore.settings.customModelsPath}/${item.name}`, true);
}} /> }} />
} }
{item.downloadUrl && !item.isLocal && {item.downloadUrl && !item.isComplete &&
<ToolTipButton desc={t('Download')} icon={<ArrowDownload20Regular />} onClick={() => { <ToolTipButton desc={t('Download')} icon={<ArrowDownload20Regular />} onClick={() => {
toastWithButton(`${t('Downloading')} ${item.name}`, t('Check'), () => { toastWithButton(`${t('Downloading')} ${item.name}`, t('Check'), () => {
navigate({ pathname: '/downloads' }); navigate({ pathname: '/downloads' });
@ -203,7 +205,7 @@ export const Models: FC = observer(() => {
<div className="overflow-y-auto overflow-x-hidden"> <div className="overflow-y-auto overflow-x-hidden">
<DataGridBody<ModelSourceItem>> <DataGridBody<ModelSourceItem>>
{({ item, rowId }) => ( {({ item, rowId }) => (
(!item.hide || item.isLocal) && (!item.hide || item.isComplete) &&
<DataGridRow<ModelSourceItem> key={rowId}> <DataGridRow<ModelSourceItem> key={rowId}>
{({ renderCell }) => ( {({ renderCell }) => (
<DataGridCell>{renderCell(item)}</DataGridCell> <DataGridCell>{renderCell(item)}</DataGridCell>

View File

@ -16,7 +16,7 @@ export async function startup() {
await GetPlatform().then(p => commonStore.setPlatform(p as Platform)); await GetPlatform().then(p => commonStore.setPlatform(p as Platform));
await initConfig(); 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 if (commonStore.settings.autoUpdatesCheck) // depends on config settings
checkUpdate(); checkUpdate();
@ -58,11 +58,11 @@ async function initConfig() {
}); });
} }
async function initCache() { async function initCache(initUnfinishedModels: boolean) {
await ReadJson('cache.json').then((cacheData: Cache) => { await ReadJson('cache.json').then((cacheData: Cache) => {
if (cacheData.depComplete) if (cacheData.depComplete)
commonStore.setDepComplete(cacheData.depComplete); commonStore.setDepComplete(cacheData.depComplete);
}).catch(() => { }).catch(() => {
}); });
await refreshModels(false); await refreshModels(false, initUnfinishedModels);
} }

View File

@ -55,6 +55,7 @@ class CommonStore {
modelSourceList: ModelSourceItem[] = []; modelSourceList: ModelSourceItem[] = [];
// downloads // downloads
downloadList: DownloadStatus[] = []; downloadList: DownloadStatus[] = [];
lastUnfinishedModelDownloads: DownloadStatus[] = [];
// settings // settings
advancedCollapsed: boolean = true; advancedCollapsed: boolean = true;
settings: SettingsType = { settings: SettingsType = {
@ -197,6 +198,10 @@ class CommonStore {
setAdvancedCollapsed(value: boolean) { setAdvancedCollapsed(value: boolean) {
this.advancedCollapsed = value; this.advancedCollapsed = value;
} }
setLastUnfinishedModelDownloads(value: DownloadStatus[]) {
this.lastUnfinishedModelDownloads = value;
}
} }
export default new CommonStore(); export default new CommonStore();

View File

@ -16,6 +16,7 @@ import { Button } from '@fluentui/react-components';
import { Language, Languages, SettingsType } from '../pages/Settings'; import { Language, Languages, SettingsType } from '../pages/Settings';
import { ModelSourceItem } from '../pages/Models'; import { ModelSourceItem } from '../pages/Models';
import { ModelConfig, ModelParameters } from '../pages/Configs'; import { ModelConfig, ModelParameters } from '../pages/Configs';
import { DownloadStatus } from '../pages/Downloads';
export type Cache = { export type Cache = {
models: ModelSourceItem[] models: ModelSourceItem[]
@ -47,9 +48,11 @@ export async function refreshBuiltInModels(readCache: boolean = false) {
return cache; 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) 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) => { await ListDirFiles(commonStore.settings.customModelsPath).then((data) => {
cache.models.push(...data.flatMap(d => { cache.models.push(...data.flatMap(d => {
@ -58,8 +61,9 @@ export async function refreshLocalModels(cache: { models: ModelSourceItem[] }, f
name: d.name, name: d.name,
size: d.size, size: d.size,
lastUpdated: d.modTime, lastUpdated: d.modTime,
isComplete: true,
isLocal: true isLocal: true
}]; }] as ModelSourceItem[];
return []; return [];
})); }));
}).catch(() => { }).catch(() => {
@ -80,17 +84,43 @@ export async function refreshLocalModels(cache: { models: ModelSourceItem[] }, f
} else { } else {
cache.models[i] = Object.assign({}, cache.models[j], cache.models[i]); 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); cache.models.splice(j, 1);
j--; j--;
} }
} }
} }
commonStore.setModelSourceList(cache.models); commonStore.setModelSourceList(cache.models);
if (initUnfinishedModels)
initLastUnfinishedModelDownloads();
await saveCache().catch(() => { 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[] }) { export async function refreshRemoteModels(cache: { models: ModelSourceItem[] }) {
const manifestUrls = commonStore.modelSourceManifestList.split(/[,;\n]/); const manifestUrls = commonStore.modelSourceManifestList.split(/[,;\n]/);
const requests = manifestUrls.filter(url => url.endsWith('.json')).map( 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); const cache = await refreshBuiltInModels(readCache);
await refreshLocalModels(cache); await refreshLocalModels(cache, false, initUnfinishedModels);
await refreshRemoteModels(cache); await refreshRemoteModels(cache);
}; };