allow user to query for HF models

Signed-off-by: James <james@jan.ai>
This commit is contained in:
James 2023-10-04 15:30:15 -07:00 committed by Louis
parent 0bdd3713d4
commit 6f50424917
29 changed files with 450 additions and 132 deletions

View File

@ -38,10 +38,20 @@ const deleteModel = async (path) =>
}
});
const searchHfModels = async (params) =>
new Promise(async (resolve) => {
if (window.electronAPI) {
window.electronAPI
.invokePluginFunc(MODULE_PATH, "searchHfModels", params)
.then((res) => resolve(res));
}
});
// Register all the above functions and objects with the relevant extension points
export function init({ register }) {
register("getDownloadedModels", "getDownloadedModels", getDownloadedModels);
register("getAvailableModels", "getAvailableModels", getAvailableModels);
register("downloadModel", "downloadModel", downloadModel);
register("deleteModel", "deleteModel", deleteModel);
register("searchHfModels", "searchHfModels", searchHfModels);
}

View File

@ -1,6 +1,7 @@
const path = require("path");
const { readdirSync, lstatSync } = require("fs");
const { app } = require("electron");
const { listModels, listFiles, fileDownloadInfo } = require("@huggingface/hub");
const ALL_MODELS = [
{
@ -87,6 +88,49 @@ function getDownloadedModels() {
return downloadedModels;
}
const searchHfModels = async (params) => {
const result = [];
var index = 0;
for await (const model of listModels({
search: params.search,
credentials: params.credentials,
})) {
const files = await listFilesByName(model.name);
result.push({
...model,
files,
});
index++;
if (index === params.limit) break;
}
return result;
};
const listFilesByName = async (modelName) => {
const repo = { type: "model", name: modelName };
const fileDownloadInfoMap = {};
for await (const file of listFiles({
repo: repo,
})) {
if (file.type === "file" && file.path.endsWith(".bin")) {
const downloadInfo = await fileDownloadInfo({
repo: repo,
path: file.path,
});
fileDownloadInfoMap[file.path] = {
...file,
...downloadInfo,
};
}
}
return fileDownloadInfoMap;
};
function getAvailableModels() {
const downloadedModelIds = getDownloadedModels().map((model) => model.id);
return ALL_MODELS.filter((model) => {
@ -99,4 +143,5 @@ function getAvailableModels() {
module.exports = {
getDownloadedModels,
getAvailableModels,
searchHfModels,
};

View File

@ -24,5 +24,11 @@
"dist/*",
"package.json",
"README.md"
],
"dependencies": {
"@huggingface/hub": "^0.8.5"
},
"bundledDependencies": [
"@huggingface/hub"
]
}

View File

@ -1,7 +1,6 @@
"use client";
import { useAtomValue } from "jotai";
import { ReactNode } from "react";
import Welcome from "../WelcomeContainer";
import { Preferences } from "../Preferences";
import MyModelContainer from "../MyModelContainer";
@ -11,17 +10,14 @@ import {
getMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom";
import EmptyChatContainer from "../EmptyChatContainer";
import MainChat from "../MainChat";
type Props = {
children: ReactNode;
};
export default function ChatContainer({ children }: Props) {
export default function ChatContainer() {
const viewState = useAtomValue(getMainViewStateAtom);
switch (viewState) {
case MainViewState.ConversationEmptyModel:
return <EmptyChatContainer />
return <EmptyChatContainer />;
case MainViewState.ExploreModel:
return <ExploreModelContainer />;
case MainViewState.Setting:
@ -32,6 +28,6 @@ export default function ChatContainer({ children }: Props) {
case MainViewState.Welcome:
return <Welcome />;
default:
return <div className="flex flex-1 overflow-hidden">{children}</div>;
return <MainChat />;
}
}

View File

@ -1,9 +1,12 @@
import useGetAvailableModels from "@/_hooks/useGetAvailableModels";
import ExploreModelItem from "../ExploreModelItem";
import HeaderTitle from "../HeaderTitle";
import SearchBar from "../SearchBar";
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";
const tags = [
"Roleplay",
@ -17,14 +20,22 @@ const tags = [
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
const ExploreModelContainer: React.FC = () => {
const { allAvailableModels } = useGetAvailableModels();
const modelSearch = useAtomValue(modelSearchAtom);
const { modelList, getHuggingFaceModel } = useGetHuggingFaceModel();
useEffect(() => {
getHuggingFaceModel(modelSearch);
}, [modelSearch]);
return (
<div className="flex flex-col w-full h-full pl-[63px] pr-[89px] pt-[60px] overflow-y-auto">
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
<HeaderTitle title="Explore Models" />
<SearchBar placeholder="Search or HuggingFace URL" />
<div className="flex gap-x-14 mt-[38px]">
<div className="flex-1 flex-shrink-0">
<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]">
@ -39,16 +50,13 @@ const ExploreModelContainer: React.FC = () => {
))}
</fieldset>
</div>
<div className="flex-[3_3_0%]">
<h2 className="font-semibold text-xs mb-[18px]">Results</h2>
<div className="flex flex-col gap-[31px]">
{allAvailableModels.map((item) => (
<div className="flex-1 pb-4 gap-y-4 overflow-y-auto scroll">
{modelList.map((item) => (
<ExploreModelItem key={item.id} model={item} />
))}
</div>
</div>
</div>
</div>
);
};

View File

@ -2,13 +2,13 @@
import ExploreModelItemHeader from "../ExploreModelItemHeader";
import ModelVersionList from "../ModelVersionList";
import { useMemo, useState } from "react";
import { Product } from "@/_models/Product";
import { Fragment, useMemo, 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 = {
model: Product;
@ -16,8 +16,8 @@ type Props = {
const ExploreModelItem: React.FC<Props> = ({ model }) => {
const downloadAtom = useMemo(
() => atom((get) => get(modelDownloadStateAtom)[model.fileName ?? ""]),
[model.fileName ?? ""]
() => atom((get) => get(modelDownloadStateAtom)[model.name ?? ""]),
[model.name ?? ""]
);
const downloadState = useAtomValue(downloadAtom);
const { downloadModel } = useDownloadModel();
@ -28,9 +28,8 @@ const ExploreModelItem: React.FC<Props> = ({ model }) => {
<ExploreModelItemHeader
name={model.name}
status={TagType.Recommended}
total={model.totalSize}
versions={model.availableVersions}
downloadState={downloadState}
onDownloadClick={() => downloadModel(model)}
/>
<div className="flex flex-col px-[26px] py-[22px]">
<div className="flex justify-between">
@ -87,13 +86,17 @@ const ExploreModelItem: React.FC<Props> = ({ model }) => {
<span className="text-sm font-medium text-gray-500">Tags</span>
</div>
</div>
{show && <ModelVersionList />}
{model.availableVersions.length > 0 && (
<Fragment>
{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"
>
{!show ? "+ Show Available Versions" : "- Collapse"}
</button>
</Fragment>
)}
</div>
);
};

View File

@ -3,11 +3,13 @@ import PrimaryButton from "../PrimaryButton";
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter";
import { DownloadState } from "@/_models/DownloadState";
import SecondaryButton from "../SecondaryButton";
import { ModelVersion } from "@/_models/Product";
type Props = {
name: string;
total: number;
status: TagType;
versions: ModelVersion[];
size?: number;
downloadState?: DownloadState;
onDownloadClick?: () => void;
};
@ -15,30 +17,41 @@ type Props = {
const ExploreModelItemHeader: React.FC<Props> = ({
name,
status,
total,
size,
versions,
downloadState,
onDownloadClick,
}) => (
<div className="flex items-center justify-between p-4 border-b border-gray-200">
<div className="flex items-center gap-2">
<span>{name}</span>
<SimpleTag title={status} type={status} clickable={false} />
</div>
{downloadState != null ? (
}) => {
let downloadButton = (
<PrimaryButton
title={size ? `Download (${toGigabytes(size)})` : "Download"}
onClick={() => onDownloadClick?.()}
/>
);
if (downloadState != null) {
// downloading
downloadButton = (
<SecondaryButton
disabled
title={`Downloading (${formatDownloadPercentage(
downloadState.percent
)})`}
onClick={() => {}}
/>
) : (
<PrimaryButton
title={total ? `Download (${toGigabytes(total)})` : "Download"}
onClick={() => onDownloadClick?.()}
/>
)}
);
} else if (versions.length === 0) {
downloadButton = <SecondaryButton disabled title="No files available" />;
}
return (
<div className="flex items-center justify-between p-4 border-b border-gray-200">
<div className="flex items-center gap-2">
<span>{name}</span>
<SimpleTag title={status} type={status} clickable={false} />
</div>
{downloadButton}
</div>
);
};
export default ExploreModelItemHeader;

View File

@ -18,14 +18,14 @@ const HistoryList: React.FC = () => {
}, []);
return (
<div className="flex flex-col flex-grow pt-3 gap-2">
<div className="flex flex-col flex-grow pt-3 gap-2 overflow-hidden">
<ExpandableHeader
title="CHAT HISTORY"
expanded={expand}
onClick={() => setExpand(!expand)}
/>
<div
className={`flex flex-col gap-1 mt-1 ${!expand ? "hidden " : "block"}`}
className={`flex flex-col gap-1 mt-1 overflow-y-auto scroll ${!expand ? "hidden " : "block"}`}
>
{conversations.length > 0 ? (
conversations

View File

@ -6,7 +6,7 @@ import HistoryList from "../HistoryList";
import NewChatButton from "../NewChatButton";
const LeftContainer: React.FC = () => (
<div className="w-[323px] flex-shrink-0 p-3 h-screen border-r border-gray-200 flex flex-col">
<div className="w-[323px] flex-shrink-0 py-3 h-screen border-r border-gray-200 flex flex-col">
<SidebarHeader />
<NewChatButton />
<HistoryList />

View File

@ -1,25 +1,57 @@
import React from "react";
import { toGigabytes } from "@/_utils/converter";
import React, { useMemo } from "react";
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter";
import Image from "next/image";
import { ModelVersion, Product } from "@/_models/Product";
import useDownloadModel from "@/_hooks/useDownloadModel";
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
import { atom, useAtomValue } from "jotai";
type Props = {
title: string;
totalSizeInByte: number;
model: Product;
modelVersion: ModelVersion;
};
const ModelVersionItem: React.FC<Props> = ({ title, totalSizeInByte }) => (
<div className="flex justify-between items-center gap-4 pl-[13px] pt-[13px] pr-[17px] pb-3 border-t border-gray-200 first:border-t-0">
const ModelVersionItem: React.FC<Props> = ({ model, modelVersion }) => {
const { downloadHfModel } = useDownloadModel();
const downloadAtom = useMemo(
() => atom((get) => get(modelDownloadStateAtom)[modelVersion.path ?? ""]),
[modelVersion.path ?? ""]
);
const downloadState = useAtomValue(downloadAtom);
const onDownloadClick = () => {
downloadHfModel(model, modelVersion);
};
let downloadButton = (
<button
className="text-indigo-600 text-sm font-medium"
onClick={onDownloadClick}
>
Download
</button>
);
if (downloadState) {
downloadButton = (
<div>{formatDownloadPercentage(downloadState.percent)}</div>
);
}
return (
<div className="flex justify-between items-center gap-4 pl-3 pt-3 pr-4 pb-3 border-t border-gray-200 first:border-t-0">
<div className="flex items-center gap-4">
<Image src={"/icons/app_icon.svg"} width={14} height={20} alt="" />
<span className="font-sm text-gray-900">{title}</span>
<span className="font-sm text-gray-900">{modelVersion.path}</span>
</div>
<div className="flex items-center gap-4">
<div className="px-[10px] py-0.5 bg-gray-200 text-xs font-medium rounded">
{toGigabytes(totalSizeInByte)}
<div className="px-2.5 py-0.5 bg-gray-200 text-xs font-medium rounded">
{toGigabytes(modelVersion.size)}
</div>
<button className="text-indigo-600 text-sm font-medium">Download</button>
{downloadButton}
</div>
</div>
);
};
export default ModelVersionItem;

View File

@ -1,38 +1,21 @@
import React from "react";
import ModelVersionItem from "../ModelVersionItem";
import { ModelVersion, Product } from "@/_models/Product";
const data = [
{
name: "Q4_K_M.gguf",
total: 5600,
},
{
name: "Q4_K_M.gguf",
total: 5600,
},
{
name: "Q4_K_M.gguf",
total: 5600,
},
];
type Props = {
model: Product;
versions: ModelVersion[];
};
const ModelVersionList: React.FC = () => {
return (
const ModelVersionList: React.FC<Props> = ({ model, versions }) => (
<div className="px-4 py-5 border-t border-gray-200">
<div className="text-sm font-medium text-gray-500">
Available Versions
</div>
<div className="text-sm font-medium text-gray-500">Available Versions</div>
<div className="border border-gray-200 rounded-lg overflow-hidden">
{data.map((item, index) => (
<ModelVersionItem
key={index}
title={item.name}
totalSizeInByte={item.total}
/>
{versions.map((item) => (
<ModelVersionItem key={item.path} model={model} modelVersion={item} />
))}
</div>
</div>
);
};
export default ModelVersionList;

View File

@ -32,7 +32,7 @@ const NewChatButton: React.FC = () => {
};
return (
<SecondaryButton title={"New Chat"} onClick={onClick} className="my-5" />
<SecondaryButton title={"New Chat"} onClick={onClick} className="my-5 mx-3" />
);
};

View File

@ -14,7 +14,7 @@ const PrimaryButton: React.FC<Props> = ({
<button
onClick={onClick}
type="button"
className={`rounded-md bg-indigo-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-50 ${
className={`rounded-md bg-indigo-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-50 line-clamp-1 flex-shrink-0 ${
fullWidth ? "flex-1 " : ""
}}`}
>

View File

@ -1,12 +1,9 @@
import ChatContainer from "../ChatContainer";
import MainChat from "../MainChat";
import MonitorBar from "../MonitorBar";
const RightContainer = () => (
<div className="flex flex-col flex-1 h-screen">
<ChatContainer>
<MainChat />
</ChatContainer>
<ChatContainer />
<MonitorBar />
</div>
);

View File

@ -1,14 +1,34 @@
import { searchAtom } from "@/_helpers/JotaiWrapper";
import { modelSearchAtom } from "@/_helpers/JotaiWrapper";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { useSetAtom } from "jotai";
import { useState } from "react";
export enum SearchType {
Model = "model",
}
type Props = {
type?: SearchType;
placeholder?: string;
};
const SearchBar: React.FC<Props> = ({ placeholder }) => {
const setText = useSetAtom(searchAtom);
const SearchBar: React.FC<Props> = ({ type, placeholder }) => {
const [searchValue, setSearchValue] = useState("");
const setModelSearch = useSetAtom(modelSearchAtom);
let placeholderText = placeholder ? placeholder : "Search (⌘K)";
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
if (type === SearchType.Model) {
setModelSearch(searchValue);
}
}
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(event.target.value);
};
return (
<div className="relative mt-3 flex items-center">
<div className="absolute top-0 left-2 h-full flex items-center">
@ -23,8 +43,10 @@ const SearchBar: React.FC<Props> = ({ placeholder }) => {
type="text"
name="search"
id="search"
value={searchValue}
placeholder={placeholderText}
onChange={(e) => setText(e.target.value)}
onChange={handleChange}
onKeyDown={handleKeyDown}
className="block w-full rounded-md border-0 py-1.5 pl-8 pr-14 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
</div>

View File

@ -1,6 +1,6 @@
type Props = {
title: string;
onClick: () => void;
onClick?: () => void;
disabled?: boolean;
className?: string;
};
@ -15,7 +15,7 @@ const SecondaryButton: React.FC<Props> = ({
disabled={disabled}
type="button"
onClick={onClick}
className={`rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 ${className}`}
className={`rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 ${className} flex-shrink-0 line-clamp-1`}
>
{title}
</button>

View File

@ -2,7 +2,7 @@ import React from "react";
import SecondaryButton from "../SecondaryButton";
const SidebarFooter: React.FC = () => (
<div className="flex justify-between items-center gap-2">
<div className="flex justify-between items-center gap-2 mx-3">
<SecondaryButton
title={"Discord"}
onClick={() =>

View File

@ -1,11 +1,10 @@
import React from "react";
import Image from "next/image";
const SidebarHeader: React.FC = () => {
return (
<div className="flex flex-col gap-[10px]">
const SidebarHeader: React.FC = () => (
<div className="flex flex-col gap-[10px] px-3">
<Image src={"icons/Jan_AppIcon.svg"} width={68} height={28} alt="" />
</div>
);
};
export default SidebarHeader;
export default React.memo(SidebarHeader);

View File

@ -21,7 +21,7 @@ const menu = [
];
const SidebarMenu: React.FC = () => (
<div className="flex flex-col">
<div className="flex flex-col mx-3">
<div className="text-gray-500 text-xs font-semibold py-2 pl-2 pr-3">
Your Configurations
</div>

View File

@ -19,3 +19,5 @@ export const appDownloadProgress = atom<number>(-1);
export const searchingModelText = atom<string>("");
export const searchAtom = atom<string>("");
export const modelSearchAtom = atom<string>("");

View File

@ -19,12 +19,12 @@ const useCreateConversation = () => {
const addNewConvoState = useSetAtom(addNewConversationStateAtom);
const requestCreateConvo = async (model: Product) => {
const conversationName = model.name;
const conv: Conversation = {
image: undefined,
model_id: model.id,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
name: "Conversation",
name: conversationName,
};
const id = await executeSerial(DataService.CREATE_CONVERSATION, conv);
await initModel(model);
@ -32,7 +32,7 @@ const useCreateConversation = () => {
const mappedConvo: Conversation = {
id,
model_id: model.id,
name: "Conversation",
name: conversationName,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};

View File

@ -1,6 +1,6 @@
import { executeSerial } from "@/_services/pluginService";
import { DataService, ModelManagementService } from "../../shared/coreService";
import { Product } from "@/_models/Product";
import { ModelVersion, Product } from "@/_models/Product";
export default function useDownloadModel() {
const downloadModel = async (model: Product) => {
@ -11,7 +11,28 @@ export default function useDownloadModel() {
});
};
const downloadHfModel = async (
model: Product,
modelVersion: ModelVersion
) => {
const hfModel: Product = {
...model,
id: `${model.author}.${modelVersion.path}`,
slug: `${model.author}.${modelVersion.path}`,
name: `${model.name} - ${modelVersion.path}`,
fileName: modelVersion.path,
totalSize: modelVersion.size,
downloadUrl: modelVersion.downloadUrl,
};
await executeSerial(DataService.STORE_MODEL, hfModel);
await executeSerial(ModelManagementService.DOWNLOAD_MODEL, {
downloadUrl: hfModel.downloadUrl,
fileName: hfModel.fileName,
});
};
return {
downloadModel,
downloadHfModel,
};
}

View File

@ -1,7 +1,9 @@
import { Product } from "@/_models/Product";
import { ModelVersion, Product, ProductType } from "@/_models/Product";
import { useEffect, useState } from "react";
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
import { DataService, ModelManagementService } from "../../shared/coreService";
import { SearchModelParamHf } from "@/_models/hf/SearchModelParam.hf";
import { ListModelOutputHf } from "@/_models/hf/ListModelOutput.hf";
export function useGetDownloadedModels() {
const [downloadedModels, setDownloadedModels] = useState<Product[]>([]);
@ -28,3 +30,53 @@ export async function getModelFiles(): Promise<Product[]> {
);
return downloadedModels ?? [];
}
export async function searchHfModels(
params: SearchModelParamHf
): Promise<Product[]> {
const result: ListModelOutputHf[] = await executeSerial(
ModelManagementService.SEARCH_HF_MODELS,
params
);
const products: Product[] = result.map((model) => {
const modelVersions: ModelVersion[] = [];
for (const [, file] of Object.entries(model.files)) {
const modelVersion: ModelVersion = {
path: file.path,
type: file.type,
downloadUrl: file.downloadLink,
size: file.size,
};
modelVersions.push(modelVersion);
}
const p = {
id: model.id,
slug: model.name,
name: model.name,
description: model.name,
avatarUrl: "",
longDescription: model.name,
technicalDescription: model.name,
author: model.name.split("/")[0],
version: "1.0.0",
modelUrl: "https://google.com",
nsfw: false,
greeting: "Hello there",
type: ProductType.LLM,
createdAt: -1,
accelerated: true,
totalSize: -1,
format: "",
status: "Not downloaded",
releaseDate: -1,
availableVersions: modelVersions,
};
return p;
});
return products;
}

View File

@ -0,0 +1,25 @@
import { useState } from "react";
import { searchHfModels } from "./useGetDownloadedModels";
import { SearchModelParamHf } from "@/_models/hf/SearchModelParam.hf";
import { Product } from "@/_models/Product";
export default function useGetHuggingFaceModel() {
const [modelList, setModelList] = useState<Product[]>([]);
const getHuggingFaceModel = async (owner?: string) => {
if (!owner) {
setModelList([]);
return;
}
const searchParams: SearchModelParamHf = {
search: { owner },
limit: 5,
};
const result = await searchHfModels(searchParams);
console.debug("result", JSON.stringify(result));
setModelList(result);
};
return { modelList, getHuggingFaceModel };
}

View File

@ -33,4 +33,28 @@ export interface Product {
format: string; // TODO: add this in the database // GGUF or something else
status: string; // TODO: add this in the database // Downloaded, Active
releaseDate: number; // TODO: add this in the database
availableVersions: ModelVersion[];
}
export interface ModelVersion {
/**
* Act as the id of the model version
*/
path: string;
/**
* currently, we only have `file` type
*/
type: string;
/**
* The download url for the model version
*/
downloadUrl: string;
/**
* File size in bytes
*/
size: number;
}

View File

@ -0,0 +1,24 @@
import { Task } from "./SearchModelParam.hf";
export type ListModelOutputHf = {
id: string;
name: string;
private: boolean;
task: Task;
downloads: number;
gated: boolean;
likes: number;
updatedAt?: number | null;
files: Map<string, FileInfo>;
};
export type FileInfo = {
type: string;
oid: string;
size: number;
lfs: Map<string, unknown>;
path: string;
etag: string;
downloadLink: string;
};

View File

@ -0,0 +1,53 @@
export type SearchModelParamHf = {
search?: {
owner?: string;
task?: Task;
};
credentials?: {
accessToken: string;
};
limit: number;
};
export type Task =
| "text-classification"
| "token-classification"
| "table-question-answering"
| "question-answering"
| "zero-shot-classification"
| "translation"
| "summarization"
| "conversational"
| "feature-extraction"
| "text-generation"
| "text2text-generation"
| "fill-mask"
| "sentence-similarity"
| "text-to-speech"
| "automatic-speech-recognition"
| "audio-to-audio"
| "audio-classification"
| "voice-activity-detection"
| "depth-estimation"
| "image-classification"
| "object-detection"
| "image-segmentation"
| "text-to-image"
| "image-to-text"
| "image-to-image"
| "unconditional-image-generation"
| "video-classification"
| "reinforcement-learning"
| "robotics"
| "tabular-classification"
| "tabular-regression"
| "tabular-to-text"
| "table-to-text"
| "multiple-choice"
| "text-retrieval"
| "time-series-forecasting"
| "visual-question-answering"
| "document-question-answering"
| "zero-shot-image-classification"
| "graph-ml"
| "other";

View File

@ -3,7 +3,9 @@ export const isToday = (timestamp: number) => {
return today.setHours(0, 0, 0, 0) == new Date(timestamp).setHours(0, 0, 0, 0);
};
export const displayDate = (timestamp: number) => {
export const displayDate = (timestamp?: number) => {
if (!timestamp) return "N/A";
let displayDate = new Date(timestamp).toLocaleString();
if (isToday(timestamp)) {
displayDate = new Date(timestamp).toLocaleTimeString(undefined, {

View File

@ -37,6 +37,7 @@ export enum ModelManagementService {
GET_AVAILABLE_MODELS = "getAvailableModels",
DELETE_MODEL = "deleteModel",
DOWNLOAD_MODEL = "downloadModel",
SEARCH_HF_MODELS = "searchHfModels",
}
export enum PreferenceService {