Adding downloading model table

Signed-off-by: James <james@jan.ai>
This commit is contained in:
James 2023-10-04 19:35:12 -07:00 committed by Louis
parent 6f50424917
commit 223a95ef3d
10 changed files with 163 additions and 56 deletions

View File

@ -0,0 +1,27 @@
import React, { Fragment } from "react";
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
import { useAtomValue } from "jotai";
import ModelDownloadingTable from "../ModelDownloadingTable";
import { DownloadState } from "@/_models/DownloadState";
const DownloadingModelTable: React.FC = () => {
const modelDownloadState = useAtomValue(modelDownloadStateAtom);
const isAnyModelDownloading = Object.values(modelDownloadState).length > 0;
if (!isAnyModelDownloading) return null;
const downloadStates: DownloadState[] = [];
for (const [, value] of Object.entries(modelDownloadState)) {
downloadStates.push(value);
}
return (
<Fragment>
<h3 className="text-xl leading-[25px] mt-[50px] mb-4">Downloading Models</h3>
<ModelDownloadingTable downloadStates={downloadStates} />
</Fragment>
);
};
export default DownloadingModelTable;

View File

@ -1,12 +1,8 @@
import ExploreModelItem from "../ExploreModelItem";
import HeaderTitle from "../HeaderTitle";
import SearchBar, { SearchType } from "../SearchBar";
import SimpleCheckbox from "../SimpleCheckbox";
import SimpleTag, { TagType } from "../SimpleTag";
import useGetHuggingFaceModel from "@/_hooks/useGetHuggingFaceModel";
import { useAtomValue } from "jotai";
import { modelSearchAtom } from "@/_helpers/JotaiWrapper";
import { useEffect } from "react";
import ExploreModelList from "../ExploreModelList";
const tags = [
"Roleplay",
@ -19,45 +15,32 @@ const tags = [
];
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
const ExploreModelContainer: React.FC = () => {
const modelSearch = useAtomValue(modelSearchAtom);
const { modelList, getHuggingFaceModel } = useGetHuggingFaceModel();
useEffect(() => {
getHuggingFaceModel(modelSearch);
}, [modelSearch]);
return (
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
<HeaderTitle title="Explore Models" />
<SearchBar
type={SearchType.Model}
placeholder="Owner name like TheBloke, etc.."
/>
<div className="flex flex-1 gap-x-10 mt-9 overflow-hidden">
<div className="w-64">
<h2 className="font-semibold text-xs mb-[15px]">Tags</h2>
<SearchBar placeholder="Filter by tags" />
<div className="flex flex-wrap gap-[9px] mt-[14px]">
{tags.map((item) => (
<SimpleTag key={item} title={item} type={item as TagType} />
))}
</div>
<hr className="my-10" />
<fieldset>
{checkboxs.map((item) => (
<SimpleCheckbox key={item} name={item} />
))}
</fieldset>
</div>
<div className="flex-1 pb-4 gap-y-4 overflow-y-auto scroll">
{modelList.map((item) => (
<ExploreModelItem key={item.id} model={item} />
const ExploreModelContainer: React.FC = () => (
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
<HeaderTitle title="Explore Models" />
<SearchBar
type={SearchType.Model}
placeholder="Owner name like TheBloke, etc.."
/>
<div className="flex flex-1 gap-x-10 mt-9 overflow-hidden">
<div className="w-64">
<h2 className="font-semibold text-xs mb-[15px]">Tags</h2>
<SearchBar placeholder="Filter by tags" />
<div className="flex flex-wrap gap-[9px] mt-[14px]">
{tags.map((item) => (
<SimpleTag key={item} title={item} type={item as TagType} />
))}
</div>
<hr className="my-10" />
<fieldset>
{checkboxs.map((item) => (
<SimpleCheckbox key={item} name={item} />
))}
</fieldset>
</div>
<ExploreModelList />
</div>
);
};
</div>
);
export default ExploreModelContainer;

View File

@ -2,12 +2,9 @@
import ExploreModelItemHeader from "../ExploreModelItemHeader";
import ModelVersionList from "../ModelVersionList";
import { Fragment, useMemo, useState } from "react";
import { Fragment, useState } from "react";
import SimpleTag, { TagType } from "../SimpleTag";
import { displayDate } from "@/_utils/datetime";
import useDownloadModel from "@/_hooks/useDownloadModel";
import { atom, useAtomValue } from "jotai";
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
import { Product } from "@/_models/Product";
type Props = {
@ -15,21 +12,14 @@ type Props = {
};
const ExploreModelItem: React.FC<Props> = ({ model }) => {
const downloadAtom = useMemo(
() => atom((get) => get(modelDownloadStateAtom)[model.name ?? ""]),
[model.name ?? ""]
);
const downloadState = useAtomValue(downloadAtom);
const { downloadModel } = useDownloadModel();
const [show, setShow] = useState(false);
return (
<div className="flex flex-col border border-gray-200 rounded-[5px]">
<div className="flex flex-col border border-gray-200 rounded-md mb-4">
<ExploreModelItemHeader
name={model.name}
status={TagType.Recommended}
versions={model.availableVersions}
downloadState={downloadState}
/>
<div className="flex flex-col px-[26px] py-[22px]">
<div className="flex justify-between">
@ -88,7 +78,12 @@ const ExploreModelItem: React.FC<Props> = ({ model }) => {
</div>
{model.availableVersions.length > 0 && (
<Fragment>
{show && <ModelVersionList model={model} versions={model.availableVersions} />}
{show && (
<ModelVersionList
model={model}
versions={model.availableVersions}
/>
)}
<button
onClick={() => setShow(!show)}
className="bg-[#FBFBFB] text-gray-500 text-sm text-left py-2 px-4 border-t border-gray-200"

View File

@ -0,0 +1,24 @@
import React, { useEffect } from "react";
import ExploreModelItem from "../ExploreModelItem";
import { modelSearchAtom } from "@/_helpers/JotaiWrapper";
import useGetHuggingFaceModel from "@/_hooks/useGetHuggingFaceModel";
import { useAtomValue } from "jotai";
const ExploreModelList: React.FC = () => {
const modelSearch = useAtomValue(modelSearchAtom);
const { modelList, getHuggingFaceModel } = useGetHuggingFaceModel();
useEffect(() => {
getHuggingFaceModel(modelSearch);
}, [modelSearch]);
return (
<div className="flex-1 overflow-y-auto scroll">
{modelList.map((item) => (
<ExploreModelItem key={item.id} model={item} />
))}
</div>
);
};
export default ExploreModelList;

View File

@ -0,0 +1,37 @@
import React from "react";
import { DownloadState } from "@/_models/DownloadState";
import {
formatDownloadPercentage,
formatDownloadSpeed,
toGigabytes,
} from "@/_utils/converter";
type Props = {
downloadState: DownloadState;
};
const ModelDownloadingRow: React.FC<Props> = ({ downloadState }) => (
<tr
className="border-b border-gray-200 last:border-b-0 last:rounded-lg"
key={downloadState.fileName}
>
<td className="flex flex-col whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-900">
{downloadState.fileName}
<span className="text-gray-500 font-normal">model.version</span>
</td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{toGigabytes(downloadState.size.transferred)}
</td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{toGigabytes(downloadState.size.total)}
</td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{formatDownloadPercentage(downloadState.percent)}
</td>
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
{formatDownloadSpeed(downloadState.speed)}
</td>
</tr>
);
export default ModelDownloadingRow;

View File

@ -0,0 +1,34 @@
import React from "react";
import ModelTableHeader from "../ModelTableHeader";
import { DownloadState } from "@/_models/DownloadState";
import ModelDownloadingRow from "../ModelDownloadingRow";
type Props = {
downloadStates: DownloadState[];
};
const tableHeaders = ["MODEL", "TRANSFERRED", "SIZE", "PERCENTAGE", "SPEED"];
const ModelDownloadingTable: React.FC<Props> = ({ downloadStates }) => (
<div className="flow-root border rounded-lg border-gray-200 min-w-full align-middle shadow-lg">
<table className="min-w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<tr className="rounded-t-lg">
{tableHeaders.map((item) => (
<ModelTableHeader key={item} title={item} />
))}
<th scope="col" className="relative px-6 py-3 w-fit">
<span className="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody>
{downloadStates.map((state) => (
<ModelDownloadingRow key={state.fileName} downloadState={state} />
))}
</tbody>
</table>
</div>
);
export default React.memo(ModelDownloadingTable);

View File

@ -10,7 +10,7 @@ type Props = {
const tableHeaders = ["MODEL", "FORMAT", "SIZE", "STATUS", "ACTIONS"];
const ModelTable: React.FC<Props> = ({ models }) => (
<div className="flow-root inline-block border rounded-lg border-gray-200 min-w-full align-middle shadow-lg">
<div className="flow-root border rounded-lg border-gray-200 min-w-full align-middle shadow-lg">
<table className="min-w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<tr className="rounded-t-lg">

View File

@ -1,11 +1,13 @@
import HeaderTitle from "../HeaderTitle";
import DownloadedModelTable from "../DownloadedModelTable";
import ActiveModelTable from "../ActiveModelTable";
import DownloadingModelTable from "../DownloadingModelTable";
const MyModelContainer: React.FC = () => (
<div className="flex flex-col w-full h-full pl-[63px] pr-[89px] pt-[60px]">
<HeaderTitle title="My Models" />
<ActiveModelTable />
<DownloadingModelTable />
<DownloadedModelTable />
</div>
);

View File

@ -14,7 +14,7 @@ export default function useGetHuggingFaceModel() {
const searchParams: SearchModelParamHf = {
search: { owner },
limit: 5,
limit: 10,
};
const result = await searchHfModels(searchParams);
console.debug("result", JSON.stringify(result));

View File

@ -13,3 +13,8 @@ export const toGigabytes = (input: number) => {
export const formatDownloadPercentage = (input: number) => {
return (input * 100).toFixed(2) + "%";
};
export const formatDownloadSpeed = (input: number | undefined) => {
if (!input) return "0B/s";
return toGigabytes(input) + "/s";
};