From 6f5042491740e6a6bb65427d88e859ff63a57669 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 4 Oct 2023 15:30:15 -0700 Subject: [PATCH] allow user to query for HF models Signed-off-by: James --- .../plugins/model-management-plugin/index.js | 10 +++ .../plugins/model-management-plugin/module.js | 45 +++++++++++++ .../model-management-plugin/package.json | 6 ++ web/app/_components/ChatContainer/index.tsx | 12 ++-- .../ExploreModelContainer/index.tsx | 36 +++++++---- .../_components/ExploreModelItem/index.tsx | 29 +++++---- .../ExploreModelItemHeader/index.tsx | 49 ++++++++------ web/app/_components/HistoryList/index.tsx | 4 +- web/app/_components/LeftContainer/index.tsx | 2 +- .../_components/ModelVersionItem/index.tsx | 64 ++++++++++++++----- .../_components/ModelVersionList/index.tsx | 47 +++++--------- web/app/_components/NewChatButton/index.tsx | 2 +- web/app/_components/PrimaryButton/index.tsx | 2 +- web/app/_components/RightContainer/index.tsx | 5 +- web/app/_components/SearchBar/index.tsx | 30 +++++++-- web/app/_components/SecondaryButton/index.tsx | 4 +- web/app/_components/SidebarFooter/index.tsx | 2 +- web/app/_components/SidebarHeader/index.tsx | 15 ++--- web/app/_components/SidebarMenu/index.tsx | 2 +- web/app/_helpers/JotaiWrapper.tsx | 2 + web/app/_hooks/useCreateConversation.ts | 6 +- web/app/_hooks/useDownloadModel.ts | 23 ++++++- web/app/_hooks/useGetDownloadedModels.ts | 54 +++++++++++++++- web/app/_hooks/useGetHuggingFaceModel.ts | 25 ++++++++ web/app/_models/Product.ts | 24 +++++++ web/app/_models/hf/ListModelOutput.hf.ts | 24 +++++++ web/app/_models/hf/SearchModelParam.hf.ts | 53 +++++++++++++++ web/app/_utils/datetime.ts | 4 +- web/shared/coreService.ts | 1 + 29 files changed, 450 insertions(+), 132 deletions(-) create mode 100644 web/app/_hooks/useGetHuggingFaceModel.ts create mode 100644 web/app/_models/hf/ListModelOutput.hf.ts create mode 100644 web/app/_models/hf/SearchModelParam.hf.ts diff --git a/electron/core/plugins/model-management-plugin/index.js b/electron/core/plugins/model-management-plugin/index.js index 0d2449931..1e5b57eac 100644 --- a/electron/core/plugins/model-management-plugin/index.js +++ b/electron/core/plugins/model-management-plugin/index.js @@ -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); } diff --git a/electron/core/plugins/model-management-plugin/module.js b/electron/core/plugins/model-management-plugin/module.js index e17dc59e7..a944658b2 100644 --- a/electron/core/plugins/model-management-plugin/module.js +++ b/electron/core/plugins/model-management-plugin/module.js @@ -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, }; diff --git a/electron/core/plugins/model-management-plugin/package.json b/electron/core/plugins/model-management-plugin/package.json index 6a1b61a32..bc2dc693e 100644 --- a/electron/core/plugins/model-management-plugin/package.json +++ b/electron/core/plugins/model-management-plugin/package.json @@ -24,5 +24,11 @@ "dist/*", "package.json", "README.md" + ], + "dependencies": { + "@huggingface/hub": "^0.8.5" + }, + "bundledDependencies": [ + "@huggingface/hub" ] } diff --git a/web/app/_components/ChatContainer/index.tsx b/web/app/_components/ChatContainer/index.tsx index 9a002ab13..c55eeef2e 100644 --- a/web/app/_components/ChatContainer/index.tsx +++ b/web/app/_components/ChatContainer/index.tsx @@ -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 + return ; case MainViewState.ExploreModel: return ; case MainViewState.Setting: @@ -32,6 +28,6 @@ export default function ChatContainer({ children }: Props) { case MainViewState.Welcome: return ; default: - return
{children}
; + return ; } } diff --git a/web/app/_components/ExploreModelContainer/index.tsx b/web/app/_components/ExploreModelContainer/index.tsx index d07745ffa..88377cbf5 100644 --- a/web/app/_components/ExploreModelContainer/index.tsx +++ b/web/app/_components/ExploreModelContainer/index.tsx @@ -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 ( -
+
- -
-
+ +
+

Tags

@@ -39,13 +50,10 @@ const ExploreModelContainer: React.FC = () => { ))}
-
-

Results

-
- {allAvailableModels.map((item) => ( - - ))} -
+
+ {modelList.map((item) => ( + + ))}
diff --git a/web/app/_components/ExploreModelItem/index.tsx b/web/app/_components/ExploreModelItem/index.tsx index 35d1de650..38fd6bd92 100644 --- a/web/app/_components/ExploreModelItem/index.tsx +++ b/web/app/_components/ExploreModelItem/index.tsx @@ -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 = ({ 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 = ({ model }) => { downloadModel(model)} />
@@ -87,13 +86,17 @@ const ExploreModelItem: React.FC = ({ model }) => { Tags
- {show && } - + {model.availableVersions.length > 0 && ( + + {show && } + + + )}
); }; diff --git a/web/app/_components/ExploreModelItemHeader/index.tsx b/web/app/_components/ExploreModelItemHeader/index.tsx index 83a73d97d..4bfeea035 100644 --- a/web/app/_components/ExploreModelItemHeader/index.tsx +++ b/web/app/_components/ExploreModelItemHeader/index.tsx @@ -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 = ({ name, status, - total, + size, + versions, downloadState, onDownloadClick, -}) => ( -
-
- {name} - -
- {downloadState != null ? ( +}) => { + let downloadButton = ( + onDownloadClick?.()} + /> + ); + + if (downloadState != null) { + // downloading + downloadButton = ( {}} /> - ) : ( - onDownloadClick?.()} - /> - )} -
-); + ); + } else if (versions.length === 0) { + downloadButton = ; + } + + return ( +
+
+ {name} + +
+ {downloadButton} +
+ ); +}; export default ExploreModelItemHeader; diff --git a/web/app/_components/HistoryList/index.tsx b/web/app/_components/HistoryList/index.tsx index 391b5504b..0ce2c0c4c 100644 --- a/web/app/_components/HistoryList/index.tsx +++ b/web/app/_components/HistoryList/index.tsx @@ -18,14 +18,14 @@ const HistoryList: React.FC = () => { }, []); return ( -
+
setExpand(!expand)} />
{conversations.length > 0 ? ( conversations diff --git a/web/app/_components/LeftContainer/index.tsx b/web/app/_components/LeftContainer/index.tsx index df525ce49..d99bebafb 100644 --- a/web/app/_components/LeftContainer/index.tsx +++ b/web/app/_components/LeftContainer/index.tsx @@ -6,7 +6,7 @@ import HistoryList from "../HistoryList"; import NewChatButton from "../NewChatButton"; const LeftContainer: React.FC = () => ( -
+
diff --git a/web/app/_components/ModelVersionItem/index.tsx b/web/app/_components/ModelVersionItem/index.tsx index 355128bba..ab530a87a 100644 --- a/web/app/_components/ModelVersionItem/index.tsx +++ b/web/app/_components/ModelVersionItem/index.tsx @@ -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 = ({ title, totalSizeInByte }) => ( -
-
- - {title} -
-
-
- {toGigabytes(totalSizeInByte)} +const ModelVersionItem: React.FC = ({ 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 = ( + + ); + + if (downloadState) { + downloadButton = ( +
{formatDownloadPercentage(downloadState.percent)}
+ ); + } + + return ( +
+
+ + {modelVersion.path} +
+
+
+ {toGigabytes(modelVersion.size)} +
+ {downloadButton}
-
-
-); + ); +}; export default ModelVersionItem; diff --git a/web/app/_components/ModelVersionList/index.tsx b/web/app/_components/ModelVersionList/index.tsx index f70d38a76..09af4a623 100644 --- a/web/app/_components/ModelVersionList/index.tsx +++ b/web/app/_components/ModelVersionList/index.tsx @@ -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, - }, -]; - -const ModelVersionList: React.FC = () => { - return ( -
-
- Available Versions -
-
- {data.map((item, index) => ( - - ))} -
-
- ); +type Props = { + model: Product; + versions: ModelVersion[]; }; +const ModelVersionList: React.FC = ({ model, versions }) => ( +
+
Available Versions
+
+ {versions.map((item) => ( + + ))} +
+
+); + export default ModelVersionList; diff --git a/web/app/_components/NewChatButton/index.tsx b/web/app/_components/NewChatButton/index.tsx index 3fc7de8e8..a7fbcddc2 100644 --- a/web/app/_components/NewChatButton/index.tsx +++ b/web/app/_components/NewChatButton/index.tsx @@ -32,7 +32,7 @@ const NewChatButton: React.FC = () => { }; return ( - + ); }; diff --git a/web/app/_components/PrimaryButton/index.tsx b/web/app/_components/PrimaryButton/index.tsx index d87249b44..c87aa9717 100644 --- a/web/app/_components/PrimaryButton/index.tsx +++ b/web/app/_components/PrimaryButton/index.tsx @@ -14,7 +14,7 @@ const PrimaryButton: React.FC = ({ diff --git a/web/app/_components/SidebarFooter/index.tsx b/web/app/_components/SidebarFooter/index.tsx index 97bab2748..d3de1924e 100644 --- a/web/app/_components/SidebarFooter/index.tsx +++ b/web/app/_components/SidebarFooter/index.tsx @@ -2,7 +2,7 @@ import React from "react"; import SecondaryButton from "../SecondaryButton"; const SidebarFooter: React.FC = () => ( -
+
diff --git a/web/app/_components/SidebarHeader/index.tsx b/web/app/_components/SidebarHeader/index.tsx index d453f6fc2..9b21a2fc2 100644 --- a/web/app/_components/SidebarHeader/index.tsx +++ b/web/app/_components/SidebarHeader/index.tsx @@ -1,11 +1,10 @@ +import React from "react"; import Image from "next/image"; -const SidebarHeader: React.FC = () => { - return ( -
- -
- ); -}; +const SidebarHeader: React.FC = () => ( +
+ +
+); -export default SidebarHeader; +export default React.memo(SidebarHeader); diff --git a/web/app/_components/SidebarMenu/index.tsx b/web/app/_components/SidebarMenu/index.tsx index 2e772d427..014a96905 100644 --- a/web/app/_components/SidebarMenu/index.tsx +++ b/web/app/_components/SidebarMenu/index.tsx @@ -21,7 +21,7 @@ const menu = [ ]; const SidebarMenu: React.FC = () => ( -
+
Your Configurations
diff --git a/web/app/_helpers/JotaiWrapper.tsx b/web/app/_helpers/JotaiWrapper.tsx index 63e4a321c..0c0f97fd5 100644 --- a/web/app/_helpers/JotaiWrapper.tsx +++ b/web/app/_helpers/JotaiWrapper.tsx @@ -19,3 +19,5 @@ export const appDownloadProgress = atom(-1); export const searchingModelText = atom(""); export const searchAtom = atom(""); + +export const modelSearchAtom = atom(""); diff --git a/web/app/_hooks/useCreateConversation.ts b/web/app/_hooks/useCreateConversation.ts index fcff74956..89c80144a 100644 --- a/web/app/_hooks/useCreateConversation.ts +++ b/web/app/_hooks/useCreateConversation.ts @@ -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(), }; diff --git a/web/app/_hooks/useDownloadModel.ts b/web/app/_hooks/useDownloadModel.ts index d29697a7d..de2ce898b 100644 --- a/web/app/_hooks/useDownloadModel.ts +++ b/web/app/_hooks/useDownloadModel.ts @@ -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, }; } diff --git a/web/app/_hooks/useGetDownloadedModels.ts b/web/app/_hooks/useGetDownloadedModels.ts index 90cfa00a4..4b837e6bb 100644 --- a/web/app/_hooks/useGetDownloadedModels.ts +++ b/web/app/_hooks/useGetDownloadedModels.ts @@ -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([]); @@ -28,3 +30,53 @@ export async function getModelFiles(): Promise { ); return downloadedModels ?? []; } + +export async function searchHfModels( + params: SearchModelParamHf +): Promise { + 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; +} diff --git a/web/app/_hooks/useGetHuggingFaceModel.ts b/web/app/_hooks/useGetHuggingFaceModel.ts new file mode 100644 index 000000000..d1d8ddeda --- /dev/null +++ b/web/app/_hooks/useGetHuggingFaceModel.ts @@ -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([]); + + 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 }; +} diff --git a/web/app/_models/Product.ts b/web/app/_models/Product.ts index dad227bd3..d16d5a9b0 100644 --- a/web/app/_models/Product.ts +++ b/web/app/_models/Product.ts @@ -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; } diff --git a/web/app/_models/hf/ListModelOutput.hf.ts b/web/app/_models/hf/ListModelOutput.hf.ts new file mode 100644 index 000000000..3db7293a2 --- /dev/null +++ b/web/app/_models/hf/ListModelOutput.hf.ts @@ -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; +}; + +export type FileInfo = { + type: string; + oid: string; + size: number; + lfs: Map; + path: string; + etag: string; + downloadLink: string; +}; diff --git a/web/app/_models/hf/SearchModelParam.hf.ts b/web/app/_models/hf/SearchModelParam.hf.ts new file mode 100644 index 000000000..1ba9811f4 --- /dev/null +++ b/web/app/_models/hf/SearchModelParam.hf.ts @@ -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"; diff --git a/web/app/_utils/datetime.ts b/web/app/_utils/datetime.ts index a1c39eeae..2862ad1e7 100644 --- a/web/app/_utils/datetime.ts +++ b/web/app/_utils/datetime.ts @@ -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, { diff --git a/web/shared/coreService.ts b/web/shared/coreService.ts index 1de8f95ca..b0f5554a1 100644 --- a/web/shared/coreService.ts +++ b/web/shared/coreService.ts @@ -37,6 +37,7 @@ export enum ModelManagementService { GET_AVAILABLE_MODELS = "getAvailableModels", DELETE_MODEL = "deleteModel", DOWNLOAD_MODEL = "downloadModel", + SEARCH_HF_MODELS = "searchHfModels", } export enum PreferenceService {