Adding downloading model table
Signed-off-by: James <james@jan.ai>
This commit is contained in:
parent
6f50424917
commit
223a95ef3d
27
web/app/_components/DownloadingModelTable/index.tsx
Normal file
27
web/app/_components/DownloadingModelTable/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
|
||||
24
web/app/_components/ExploreModelList/index.tsx
Normal file
24
web/app/_components/ExploreModelList/index.tsx
Normal 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;
|
||||
37
web/app/_components/ModelDownloadingRow/index.tsx
Normal file
37
web/app/_components/ModelDownloadingRow/index.tsx
Normal 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;
|
||||
34
web/app/_components/ModelDownloadingTable/index.tsx
Normal file
34
web/app/_components/ModelDownloadingTable/index.tsx
Normal 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);
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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";
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user