allow user to query for HF models
Signed-off-by: James <james@jan.ai>
This commit is contained in:
parent
0bdd3713d4
commit
6f50424917
@ -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
|
// Register all the above functions and objects with the relevant extension points
|
||||||
export function init({ register }) {
|
export function init({ register }) {
|
||||||
register("getDownloadedModels", "getDownloadedModels", getDownloadedModels);
|
register("getDownloadedModels", "getDownloadedModels", getDownloadedModels);
|
||||||
register("getAvailableModels", "getAvailableModels", getAvailableModels);
|
register("getAvailableModels", "getAvailableModels", getAvailableModels);
|
||||||
register("downloadModel", "downloadModel", downloadModel);
|
register("downloadModel", "downloadModel", downloadModel);
|
||||||
register("deleteModel", "deleteModel", deleteModel);
|
register("deleteModel", "deleteModel", deleteModel);
|
||||||
|
register("searchHfModels", "searchHfModels", searchHfModels);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { readdirSync, lstatSync } = require("fs");
|
const { readdirSync, lstatSync } = require("fs");
|
||||||
const { app } = require("electron");
|
const { app } = require("electron");
|
||||||
|
const { listModels, listFiles, fileDownloadInfo } = require("@huggingface/hub");
|
||||||
|
|
||||||
const ALL_MODELS = [
|
const ALL_MODELS = [
|
||||||
{
|
{
|
||||||
@ -87,6 +88,49 @@ function getDownloadedModels() {
|
|||||||
return downloadedModels;
|
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() {
|
function getAvailableModels() {
|
||||||
const downloadedModelIds = getDownloadedModels().map((model) => model.id);
|
const downloadedModelIds = getDownloadedModels().map((model) => model.id);
|
||||||
return ALL_MODELS.filter((model) => {
|
return ALL_MODELS.filter((model) => {
|
||||||
@ -99,4 +143,5 @@ function getAvailableModels() {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
getDownloadedModels,
|
getDownloadedModels,
|
||||||
getAvailableModels,
|
getAvailableModels,
|
||||||
|
searchHfModels,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,5 +24,11 @@
|
|||||||
"dist/*",
|
"dist/*",
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md"
|
"README.md"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@huggingface/hub": "^0.8.5"
|
||||||
|
},
|
||||||
|
"bundledDependencies": [
|
||||||
|
"@huggingface/hub"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { ReactNode } from "react";
|
|
||||||
import Welcome from "../WelcomeContainer";
|
import Welcome from "../WelcomeContainer";
|
||||||
import { Preferences } from "../Preferences";
|
import { Preferences } from "../Preferences";
|
||||||
import MyModelContainer from "../MyModelContainer";
|
import MyModelContainer from "../MyModelContainer";
|
||||||
@ -11,17 +10,14 @@ import {
|
|||||||
getMainViewStateAtom,
|
getMainViewStateAtom,
|
||||||
} from "@/_helpers/atoms/MainView.atom";
|
} from "@/_helpers/atoms/MainView.atom";
|
||||||
import EmptyChatContainer from "../EmptyChatContainer";
|
import EmptyChatContainer from "../EmptyChatContainer";
|
||||||
|
import MainChat from "../MainChat";
|
||||||
|
|
||||||
type Props = {
|
export default function ChatContainer() {
|
||||||
children: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ChatContainer({ children }: Props) {
|
|
||||||
const viewState = useAtomValue(getMainViewStateAtom);
|
const viewState = useAtomValue(getMainViewStateAtom);
|
||||||
|
|
||||||
switch (viewState) {
|
switch (viewState) {
|
||||||
case MainViewState.ConversationEmptyModel:
|
case MainViewState.ConversationEmptyModel:
|
||||||
return <EmptyChatContainer />
|
return <EmptyChatContainer />;
|
||||||
case MainViewState.ExploreModel:
|
case MainViewState.ExploreModel:
|
||||||
return <ExploreModelContainer />;
|
return <ExploreModelContainer />;
|
||||||
case MainViewState.Setting:
|
case MainViewState.Setting:
|
||||||
@ -32,6 +28,6 @@ export default function ChatContainer({ children }: Props) {
|
|||||||
case MainViewState.Welcome:
|
case MainViewState.Welcome:
|
||||||
return <Welcome />;
|
return <Welcome />;
|
||||||
default:
|
default:
|
||||||
return <div className="flex flex-1 overflow-hidden">{children}</div>;
|
return <MainChat />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import useGetAvailableModels from "@/_hooks/useGetAvailableModels";
|
|
||||||
import ExploreModelItem from "../ExploreModelItem";
|
import ExploreModelItem from "../ExploreModelItem";
|
||||||
import HeaderTitle from "../HeaderTitle";
|
import HeaderTitle from "../HeaderTitle";
|
||||||
import SearchBar 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 { useAtomValue } from "jotai";
|
||||||
|
import { modelSearchAtom } from "@/_helpers/JotaiWrapper";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
const tags = [
|
const tags = [
|
||||||
"Roleplay",
|
"Roleplay",
|
||||||
@ -17,14 +20,22 @@ const tags = [
|
|||||||
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
|
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
|
||||||
|
|
||||||
const ExploreModelContainer: React.FC = () => {
|
const ExploreModelContainer: React.FC = () => {
|
||||||
const { allAvailableModels } = useGetAvailableModels();
|
const modelSearch = useAtomValue(modelSearchAtom);
|
||||||
|
const { modelList, getHuggingFaceModel } = useGetHuggingFaceModel();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getHuggingFaceModel(modelSearch);
|
||||||
|
}, [modelSearch]);
|
||||||
|
|
||||||
return (
|
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" />
|
<HeaderTitle title="Explore Models" />
|
||||||
<SearchBar placeholder="Search or HuggingFace URL" />
|
<SearchBar
|
||||||
<div className="flex gap-x-14 mt-[38px]">
|
type={SearchType.Model}
|
||||||
<div className="flex-1 flex-shrink-0">
|
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>
|
<h2 className="font-semibold text-xs mb-[15px]">Tags</h2>
|
||||||
<SearchBar placeholder="Filter by tags" />
|
<SearchBar placeholder="Filter by tags" />
|
||||||
<div className="flex flex-wrap gap-[9px] mt-[14px]">
|
<div className="flex flex-wrap gap-[9px] mt-[14px]">
|
||||||
@ -39,13 +50,10 @@ const ExploreModelContainer: React.FC = () => {
|
|||||||
))}
|
))}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-[3_3_0%]">
|
<div className="flex-1 pb-4 gap-y-4 overflow-y-auto scroll">
|
||||||
<h2 className="font-semibold text-xs mb-[18px]">Results</h2>
|
{modelList.map((item) => (
|
||||||
<div className="flex flex-col gap-[31px]">
|
<ExploreModelItem key={item.id} model={item} />
|
||||||
{allAvailableModels.map((item) => (
|
))}
|
||||||
<ExploreModelItem key={item.id} model={item} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import ExploreModelItemHeader from "../ExploreModelItemHeader";
|
import ExploreModelItemHeader from "../ExploreModelItemHeader";
|
||||||
import ModelVersionList from "../ModelVersionList";
|
import ModelVersionList from "../ModelVersionList";
|
||||||
import { useMemo, useState } from "react";
|
import { Fragment, useMemo, useState } from "react";
|
||||||
import { Product } from "@/_models/Product";
|
|
||||||
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 useDownloadModel from "@/_hooks/useDownloadModel";
|
||||||
import { atom, useAtomValue } from "jotai";
|
import { atom, useAtomValue } from "jotai";
|
||||||
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
||||||
|
import { Product } from "@/_models/Product";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: Product;
|
model: Product;
|
||||||
@ -16,8 +16,8 @@ type Props = {
|
|||||||
|
|
||||||
const ExploreModelItem: React.FC<Props> = ({ model }) => {
|
const ExploreModelItem: React.FC<Props> = ({ model }) => {
|
||||||
const downloadAtom = useMemo(
|
const downloadAtom = useMemo(
|
||||||
() => atom((get) => get(modelDownloadStateAtom)[model.fileName ?? ""]),
|
() => atom((get) => get(modelDownloadStateAtom)[model.name ?? ""]),
|
||||||
[model.fileName ?? ""]
|
[model.name ?? ""]
|
||||||
);
|
);
|
||||||
const downloadState = useAtomValue(downloadAtom);
|
const downloadState = useAtomValue(downloadAtom);
|
||||||
const { downloadModel } = useDownloadModel();
|
const { downloadModel } = useDownloadModel();
|
||||||
@ -28,9 +28,8 @@ const ExploreModelItem: React.FC<Props> = ({ model }) => {
|
|||||||
<ExploreModelItemHeader
|
<ExploreModelItemHeader
|
||||||
name={model.name}
|
name={model.name}
|
||||||
status={TagType.Recommended}
|
status={TagType.Recommended}
|
||||||
total={model.totalSize}
|
versions={model.availableVersions}
|
||||||
downloadState={downloadState}
|
downloadState={downloadState}
|
||||||
onDownloadClick={() => downloadModel(model)}
|
|
||||||
/>
|
/>
|
||||||
<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">
|
||||||
@ -87,13 +86,17 @@ const ExploreModelItem: React.FC<Props> = ({ model }) => {
|
|||||||
<span className="text-sm font-medium text-gray-500">Tags</span>
|
<span className="text-sm font-medium text-gray-500">Tags</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{show && <ModelVersionList />}
|
{model.availableVersions.length > 0 && (
|
||||||
<button
|
<Fragment>
|
||||||
onClick={() => setShow(!show)}
|
{show && <ModelVersionList model={model} versions={model.availableVersions} />}
|
||||||
className="bg-[#FBFBFB] text-gray-500 text-sm text-left py-2 px-4 border-t border-gray-200"
|
<button
|
||||||
>
|
onClick={() => setShow(!show)}
|
||||||
{!show ? "+ Show Available Versions" : "- Collapse"}
|
className="bg-[#FBFBFB] text-gray-500 text-sm text-left py-2 px-4 border-t border-gray-200"
|
||||||
</button>
|
>
|
||||||
|
{!show ? "+ Show Available Versions" : "- Collapse"}
|
||||||
|
</button>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,11 +3,13 @@ import PrimaryButton from "../PrimaryButton";
|
|||||||
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter";
|
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter";
|
||||||
import { DownloadState } from "@/_models/DownloadState";
|
import { DownloadState } from "@/_models/DownloadState";
|
||||||
import SecondaryButton from "../SecondaryButton";
|
import SecondaryButton from "../SecondaryButton";
|
||||||
|
import { ModelVersion } from "@/_models/Product";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string;
|
name: string;
|
||||||
total: number;
|
|
||||||
status: TagType;
|
status: TagType;
|
||||||
|
versions: ModelVersion[];
|
||||||
|
size?: number;
|
||||||
downloadState?: DownloadState;
|
downloadState?: DownloadState;
|
||||||
onDownloadClick?: () => void;
|
onDownloadClick?: () => void;
|
||||||
};
|
};
|
||||||
@ -15,30 +17,41 @@ type Props = {
|
|||||||
const ExploreModelItemHeader: React.FC<Props> = ({
|
const ExploreModelItemHeader: React.FC<Props> = ({
|
||||||
name,
|
name,
|
||||||
status,
|
status,
|
||||||
total,
|
size,
|
||||||
|
versions,
|
||||||
downloadState,
|
downloadState,
|
||||||
onDownloadClick,
|
onDownloadClick,
|
||||||
}) => (
|
}) => {
|
||||||
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
let downloadButton = (
|
||||||
<div className="flex items-center gap-2">
|
<PrimaryButton
|
||||||
<span>{name}</span>
|
title={size ? `Download (${toGigabytes(size)})` : "Download"}
|
||||||
<SimpleTag title={status} type={status} clickable={false} />
|
onClick={() => onDownloadClick?.()}
|
||||||
</div>
|
/>
|
||||||
{downloadState != null ? (
|
);
|
||||||
|
|
||||||
|
if (downloadState != null) {
|
||||||
|
// downloading
|
||||||
|
downloadButton = (
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
disabled
|
disabled
|
||||||
title={`Downloading (${formatDownloadPercentage(
|
title={`Downloading (${formatDownloadPercentage(
|
||||||
downloadState.percent
|
downloadState.percent
|
||||||
)})`}
|
)})`}
|
||||||
onClick={() => {}}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
);
|
||||||
<PrimaryButton
|
} else if (versions.length === 0) {
|
||||||
title={total ? `Download (${toGigabytes(total)})` : "Download"}
|
downloadButton = <SecondaryButton disabled title="No files available" />;
|
||||||
onClick={() => onDownloadClick?.()}
|
}
|
||||||
/>
|
|
||||||
)}
|
return (
|
||||||
</div>
|
<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;
|
export default ExploreModelItemHeader;
|
||||||
|
|||||||
@ -18,14 +18,14 @@ const HistoryList: React.FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
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
|
<ExpandableHeader
|
||||||
title="CHAT HISTORY"
|
title="CHAT HISTORY"
|
||||||
expanded={expand}
|
expanded={expand}
|
||||||
onClick={() => setExpand(!expand)}
|
onClick={() => setExpand(!expand)}
|
||||||
/>
|
/>
|
||||||
<div
|
<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.length > 0 ? (
|
||||||
conversations
|
conversations
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import HistoryList from "../HistoryList";
|
|||||||
import NewChatButton from "../NewChatButton";
|
import NewChatButton from "../NewChatButton";
|
||||||
|
|
||||||
const LeftContainer: React.FC = () => (
|
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 />
|
<SidebarHeader />
|
||||||
<NewChatButton />
|
<NewChatButton />
|
||||||
<HistoryList />
|
<HistoryList />
|
||||||
|
|||||||
@ -1,25 +1,57 @@
|
|||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { toGigabytes } from "@/_utils/converter";
|
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter";
|
||||||
import Image from "next/image";
|
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 = {
|
type Props = {
|
||||||
title: string;
|
model: Product;
|
||||||
totalSizeInByte: number;
|
modelVersion: ModelVersion;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModelVersionItem: React.FC<Props> = ({ title, totalSizeInByte }) => (
|
const ModelVersionItem: React.FC<Props> = ({ model, modelVersion }) => {
|
||||||
<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 { downloadHfModel } = useDownloadModel();
|
||||||
<div className="flex items-center gap-4">
|
const downloadAtom = useMemo(
|
||||||
<Image src={"/icons/app_icon.svg"} width={14} height={20} alt="" />
|
() => atom((get) => get(modelDownloadStateAtom)[modelVersion.path ?? ""]),
|
||||||
<span className="font-sm text-gray-900">{title}</span>
|
[modelVersion.path ?? ""]
|
||||||
</div>
|
);
|
||||||
<div className="flex items-center gap-4">
|
const downloadState = useAtomValue(downloadAtom);
|
||||||
<div className="px-[10px] py-0.5 bg-gray-200 text-xs font-medium rounded">
|
|
||||||
{toGigabytes(totalSizeInByte)}
|
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">{modelVersion.path}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="px-2.5 py-0.5 bg-gray-200 text-xs font-medium rounded">
|
||||||
|
{toGigabytes(modelVersion.size)}
|
||||||
|
</div>
|
||||||
|
{downloadButton}
|
||||||
</div>
|
</div>
|
||||||
<button className="text-indigo-600 text-sm font-medium">Download</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
};
|
||||||
|
|
||||||
export default ModelVersionItem;
|
export default ModelVersionItem;
|
||||||
|
|||||||
@ -1,38 +1,21 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ModelVersionItem from "../ModelVersionItem";
|
import ModelVersionItem from "../ModelVersionItem";
|
||||||
|
import { ModelVersion, Product } from "@/_models/Product";
|
||||||
|
|
||||||
const data = [
|
type Props = {
|
||||||
{
|
model: Product;
|
||||||
name: "Q4_K_M.gguf",
|
versions: ModelVersion[];
|
||||||
total: 5600,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Q4_K_M.gguf",
|
|
||||||
total: 5600,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Q4_K_M.gguf",
|
|
||||||
total: 5600,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const ModelVersionList: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<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="border border-gray-200 rounded-lg overflow-hidden">
|
|
||||||
{data.map((item, index) => (
|
|
||||||
<ModelVersionItem
|
|
||||||
key={index}
|
|
||||||
title={item.name}
|
|
||||||
totalSizeInByte={item.total}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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="border border-gray-200 rounded-lg overflow-hidden">
|
||||||
|
{versions.map((item) => (
|
||||||
|
<ModelVersionItem key={item.path} model={model} modelVersion={item} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
export default ModelVersionList;
|
export default ModelVersionList;
|
||||||
|
|||||||
@ -32,7 +32,7 @@ const NewChatButton: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryButton title={"New Chat"} onClick={onClick} className="my-5" />
|
<SecondaryButton title={"New Chat"} onClick={onClick} className="my-5 mx-3" />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ const PrimaryButton: React.FC<Props> = ({
|
|||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
type="button"
|
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 " : ""
|
fullWidth ? "flex-1 " : ""
|
||||||
}}`}
|
}}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
import ChatContainer from "../ChatContainer";
|
import ChatContainer from "../ChatContainer";
|
||||||
import MainChat from "../MainChat";
|
|
||||||
import MonitorBar from "../MonitorBar";
|
import MonitorBar from "../MonitorBar";
|
||||||
|
|
||||||
const RightContainer = () => (
|
const RightContainer = () => (
|
||||||
<div className="flex flex-col flex-1 h-screen">
|
<div className="flex flex-col flex-1 h-screen">
|
||||||
<ChatContainer>
|
<ChatContainer />
|
||||||
<MainChat />
|
|
||||||
</ChatContainer>
|
|
||||||
<MonitorBar />
|
<MonitorBar />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,14 +1,34 @@
|
|||||||
import { searchAtom } from "@/_helpers/JotaiWrapper";
|
import { modelSearchAtom } from "@/_helpers/JotaiWrapper";
|
||||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export enum SearchType {
|
||||||
|
Model = "model",
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
type?: SearchType;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SearchBar: React.FC<Props> = ({ placeholder }) => {
|
const SearchBar: React.FC<Props> = ({ type, placeholder }) => {
|
||||||
const setText = useSetAtom(searchAtom);
|
const [searchValue, setSearchValue] = useState("");
|
||||||
|
const setModelSearch = useSetAtom(modelSearchAtom);
|
||||||
let placeholderText = placeholder ? placeholder : "Search (⌘K)";
|
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 (
|
return (
|
||||||
<div className="relative mt-3 flex items-center">
|
<div className="relative mt-3 flex items-center">
|
||||||
<div className="absolute top-0 left-2 h-full 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"
|
type="text"
|
||||||
name="search"
|
name="search"
|
||||||
id="search"
|
id="search"
|
||||||
|
value={searchValue}
|
||||||
placeholder={placeholderText}
|
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"
|
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>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
type Props = {
|
type Props = {
|
||||||
title: string;
|
title: string;
|
||||||
onClick: () => void;
|
onClick?: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
@ -15,7 +15,7 @@ const SecondaryButton: React.FC<Props> = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
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}
|
{title}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import SecondaryButton from "../SecondaryButton";
|
import SecondaryButton from "../SecondaryButton";
|
||||||
|
|
||||||
const SidebarFooter: React.FC = () => (
|
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
|
<SecondaryButton
|
||||||
title={"Discord"}
|
title={"Discord"}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
|
import React from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
const SidebarHeader: React.FC = () => {
|
const SidebarHeader: React.FC = () => (
|
||||||
return (
|
<div className="flex flex-col gap-[10px] px-3">
|
||||||
<div className="flex flex-col gap-[10px]">
|
<Image src={"icons/Jan_AppIcon.svg"} width={68} height={28} alt="" />
|
||||||
<Image src={"icons/Jan_AppIcon.svg"} width={68} height={28} alt="" />
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SidebarHeader;
|
export default React.memo(SidebarHeader);
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const menu = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const SidebarMenu: React.FC = () => (
|
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">
|
<div className="text-gray-500 text-xs font-semibold py-2 pl-2 pr-3">
|
||||||
Your Configurations
|
Your Configurations
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -19,3 +19,5 @@ export const appDownloadProgress = atom<number>(-1);
|
|||||||
export const searchingModelText = atom<string>("");
|
export const searchingModelText = atom<string>("");
|
||||||
|
|
||||||
export const searchAtom = atom<string>("");
|
export const searchAtom = atom<string>("");
|
||||||
|
|
||||||
|
export const modelSearchAtom = atom<string>("");
|
||||||
|
|||||||
@ -19,12 +19,12 @@ const useCreateConversation = () => {
|
|||||||
const addNewConvoState = useSetAtom(addNewConversationStateAtom);
|
const addNewConvoState = useSetAtom(addNewConversationStateAtom);
|
||||||
|
|
||||||
const requestCreateConvo = async (model: Product) => {
|
const requestCreateConvo = async (model: Product) => {
|
||||||
|
const conversationName = model.name;
|
||||||
const conv: Conversation = {
|
const conv: Conversation = {
|
||||||
image: undefined,
|
|
||||||
model_id: model.id,
|
model_id: model.id,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
name: "Conversation",
|
name: conversationName,
|
||||||
};
|
};
|
||||||
const id = await executeSerial(DataService.CREATE_CONVERSATION, conv);
|
const id = await executeSerial(DataService.CREATE_CONVERSATION, conv);
|
||||||
await initModel(model);
|
await initModel(model);
|
||||||
@ -32,7 +32,7 @@ const useCreateConversation = () => {
|
|||||||
const mappedConvo: Conversation = {
|
const mappedConvo: Conversation = {
|
||||||
id,
|
id,
|
||||||
model_id: model.id,
|
model_id: model.id,
|
||||||
name: "Conversation",
|
name: conversationName,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { executeSerial } from "@/_services/pluginService";
|
import { executeSerial } from "@/_services/pluginService";
|
||||||
import { DataService, ModelManagementService } from "../../shared/coreService";
|
import { DataService, ModelManagementService } from "../../shared/coreService";
|
||||||
import { Product } from "@/_models/Product";
|
import { ModelVersion, Product } from "@/_models/Product";
|
||||||
|
|
||||||
export default function useDownloadModel() {
|
export default function useDownloadModel() {
|
||||||
const downloadModel = async (model: Product) => {
|
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 {
|
return {
|
||||||
downloadModel,
|
downloadModel,
|
||||||
|
downloadHfModel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { Product } from "@/_models/Product";
|
import { ModelVersion, Product, ProductType } from "@/_models/Product";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
|
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
|
||||||
import { DataService, ModelManagementService } from "../../shared/coreService";
|
import { DataService, ModelManagementService } from "../../shared/coreService";
|
||||||
|
import { SearchModelParamHf } from "@/_models/hf/SearchModelParam.hf";
|
||||||
|
import { ListModelOutputHf } from "@/_models/hf/ListModelOutput.hf";
|
||||||
|
|
||||||
export function useGetDownloadedModels() {
|
export function useGetDownloadedModels() {
|
||||||
const [downloadedModels, setDownloadedModels] = useState<Product[]>([]);
|
const [downloadedModels, setDownloadedModels] = useState<Product[]>([]);
|
||||||
@ -28,3 +30,53 @@ export async function getModelFiles(): Promise<Product[]> {
|
|||||||
);
|
);
|
||||||
return downloadedModels ?? [];
|
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;
|
||||||
|
}
|
||||||
|
|||||||
25
web/app/_hooks/useGetHuggingFaceModel.ts
Normal file
25
web/app/_hooks/useGetHuggingFaceModel.ts
Normal 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 };
|
||||||
|
}
|
||||||
@ -33,4 +33,28 @@ export interface Product {
|
|||||||
format: string; // TODO: add this in the database // GGUF or something else
|
format: string; // TODO: add this in the database // GGUF or something else
|
||||||
status: string; // TODO: add this in the database // Downloaded, Active
|
status: string; // TODO: add this in the database // Downloaded, Active
|
||||||
releaseDate: number; // TODO: add this in the database
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
24
web/app/_models/hf/ListModelOutput.hf.ts
Normal file
24
web/app/_models/hf/ListModelOutput.hf.ts
Normal 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;
|
||||||
|
};
|
||||||
53
web/app/_models/hf/SearchModelParam.hf.ts
Normal file
53
web/app/_models/hf/SearchModelParam.hf.ts
Normal 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";
|
||||||
@ -3,7 +3,9 @@ export const isToday = (timestamp: number) => {
|
|||||||
return today.setHours(0, 0, 0, 0) == new Date(timestamp).setHours(0, 0, 0, 0);
|
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();
|
let displayDate = new Date(timestamp).toLocaleString();
|
||||||
if (isToday(timestamp)) {
|
if (isToday(timestamp)) {
|
||||||
displayDate = new Date(timestamp).toLocaleTimeString(undefined, {
|
displayDate = new Date(timestamp).toLocaleTimeString(undefined, {
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export enum ModelManagementService {
|
|||||||
GET_AVAILABLE_MODELS = "getAvailableModels",
|
GET_AVAILABLE_MODELS = "getAvailableModels",
|
||||||
DELETE_MODEL = "deleteModel",
|
DELETE_MODEL = "deleteModel",
|
||||||
DOWNLOAD_MODEL = "downloadModel",
|
DOWNLOAD_MODEL = "downloadModel",
|
||||||
|
SEARCH_HF_MODELS = "searchHfModels",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PreferenceService {
|
export enum PreferenceService {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user