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 HeaderTitle from "../HeaderTitle";
|
||||||
import SearchBar, { SearchType } from "../SearchBar";
|
import SearchBar, { SearchType } from "../SearchBar";
|
||||||
import SimpleCheckbox from "../SimpleCheckbox";
|
import SimpleCheckbox from "../SimpleCheckbox";
|
||||||
import SimpleTag, { TagType } from "../SimpleTag";
|
import SimpleTag, { TagType } from "../SimpleTag";
|
||||||
import useGetHuggingFaceModel from "@/_hooks/useGetHuggingFaceModel";
|
import ExploreModelList from "../ExploreModelList";
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
import { modelSearchAtom } from "@/_helpers/JotaiWrapper";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
const tags = [
|
const tags = [
|
||||||
"Roleplay",
|
"Roleplay",
|
||||||
@ -19,45 +15,32 @@ const tags = [
|
|||||||
];
|
];
|
||||||
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
|
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
|
||||||
|
|
||||||
const ExploreModelContainer: React.FC = () => {
|
const ExploreModelContainer: React.FC = () => (
|
||||||
const modelSearch = useAtomValue(modelSearchAtom);
|
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
|
||||||
const { modelList, getHuggingFaceModel } = useGetHuggingFaceModel();
|
<HeaderTitle title="Explore Models" />
|
||||||
|
<SearchBar
|
||||||
useEffect(() => {
|
type={SearchType.Model}
|
||||||
getHuggingFaceModel(modelSearch);
|
placeholder="Owner name like TheBloke, etc.."
|
||||||
}, [modelSearch]);
|
/>
|
||||||
|
<div className="flex flex-1 gap-x-10 mt-9 overflow-hidden">
|
||||||
return (
|
<div className="w-64">
|
||||||
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
|
<h2 className="font-semibold text-xs mb-[15px]">Tags</h2>
|
||||||
<HeaderTitle title="Explore Models" />
|
<SearchBar placeholder="Filter by tags" />
|
||||||
<SearchBar
|
<div className="flex flex-wrap gap-[9px] mt-[14px]">
|
||||||
type={SearchType.Model}
|
{tags.map((item) => (
|
||||||
placeholder="Owner name like TheBloke, etc.."
|
<SimpleTag key={item} title={item} type={item as TagType} />
|
||||||
/>
|
|
||||||
<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} />
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<hr className="my-10" />
|
||||||
|
<fieldset>
|
||||||
|
{checkboxs.map((item) => (
|
||||||
|
<SimpleCheckbox key={item} name={item} />
|
||||||
|
))}
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
<ExploreModelList />
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
};
|
);
|
||||||
|
|
||||||
export default ExploreModelContainer;
|
export default ExploreModelContainer;
|
||||||
|
|||||||
@ -2,12 +2,9 @@
|
|||||||
|
|
||||||
import ExploreModelItemHeader from "../ExploreModelItemHeader";
|
import ExploreModelItemHeader from "../ExploreModelItemHeader";
|
||||||
import ModelVersionList from "../ModelVersionList";
|
import ModelVersionList from "../ModelVersionList";
|
||||||
import { Fragment, useMemo, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
import SimpleTag, { TagType } from "../SimpleTag";
|
import SimpleTag, { TagType } from "../SimpleTag";
|
||||||
import { displayDate } from "@/_utils/datetime";
|
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";
|
import { Product } from "@/_models/Product";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -15,21 +12,14 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ExploreModelItem: React.FC<Props> = ({ model }) => {
|
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);
|
const [show, setShow] = useState(false);
|
||||||
|
|
||||||
return (
|
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
|
<ExploreModelItemHeader
|
||||||
name={model.name}
|
name={model.name}
|
||||||
status={TagType.Recommended}
|
status={TagType.Recommended}
|
||||||
versions={model.availableVersions}
|
versions={model.availableVersions}
|
||||||
downloadState={downloadState}
|
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col px-[26px] py-[22px]">
|
<div className="flex flex-col px-[26px] py-[22px]">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
@ -88,7 +78,12 @@ const ExploreModelItem: React.FC<Props> = ({ model }) => {
|
|||||||
</div>
|
</div>
|
||||||
{model.availableVersions.length > 0 && (
|
{model.availableVersions.length > 0 && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{show && <ModelVersionList model={model} versions={model.availableVersions} />}
|
{show && (
|
||||||
|
<ModelVersionList
|
||||||
|
model={model}
|
||||||
|
versions={model.availableVersions}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => setShow(!show)}
|
onClick={() => setShow(!show)}
|
||||||
className="bg-[#FBFBFB] text-gray-500 text-sm text-left py-2 px-4 border-t border-gray-200"
|
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 tableHeaders = ["MODEL", "FORMAT", "SIZE", "STATUS", "ACTIONS"];
|
||||||
|
|
||||||
const ModelTable: React.FC<Props> = ({ models }) => (
|
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">
|
<table className="min-w-full">
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 border-b border-gray-200">
|
||||||
<tr className="rounded-t-lg">
|
<tr className="rounded-t-lg">
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import HeaderTitle from "../HeaderTitle";
|
import HeaderTitle from "../HeaderTitle";
|
||||||
import DownloadedModelTable from "../DownloadedModelTable";
|
import DownloadedModelTable from "../DownloadedModelTable";
|
||||||
import ActiveModelTable from "../ActiveModelTable";
|
import ActiveModelTable from "../ActiveModelTable";
|
||||||
|
import DownloadingModelTable from "../DownloadingModelTable";
|
||||||
|
|
||||||
const MyModelContainer: React.FC = () => (
|
const MyModelContainer: React.FC = () => (
|
||||||
<div className="flex flex-col w-full h-full pl-[63px] pr-[89px] pt-[60px]">
|
<div className="flex flex-col w-full h-full pl-[63px] pr-[89px] pt-[60px]">
|
||||||
<HeaderTitle title="My Models" />
|
<HeaderTitle title="My Models" />
|
||||||
<ActiveModelTable />
|
<ActiveModelTable />
|
||||||
|
<DownloadingModelTable />
|
||||||
<DownloadedModelTable />
|
<DownloadedModelTable />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export default function useGetHuggingFaceModel() {
|
|||||||
|
|
||||||
const searchParams: SearchModelParamHf = {
|
const searchParams: SearchModelParamHf = {
|
||||||
search: { owner },
|
search: { owner },
|
||||||
limit: 5,
|
limit: 10,
|
||||||
};
|
};
|
||||||
const result = await searchHfModels(searchParams);
|
const result = await searchHfModels(searchParams);
|
||||||
console.debug("result", JSON.stringify(result));
|
console.debug("result", JSON.stringify(result));
|
||||||
|
|||||||
@ -13,3 +13,8 @@ export const toGigabytes = (input: number) => {
|
|||||||
export const formatDownloadPercentage = (input: number) => {
|
export const formatDownloadPercentage = (input: number) => {
|
||||||
return (input * 100).toFixed(2) + "%";
|
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