clean up
Signed-off-by: James <james@jan.ai>
This commit is contained in:
parent
e47d19e6e4
commit
9a79c3effa
@ -23,7 +23,7 @@ async function initModel(product) {
|
||||
console.error(
|
||||
"A subprocess is already running. Attempt to kill then reinit."
|
||||
);
|
||||
killSubprocess();
|
||||
dispose();
|
||||
}
|
||||
|
||||
let binaryFolder = path.join(__dirname, "nitro"); // Current directory by default
|
||||
|
||||
@ -57,7 +57,8 @@
|
||||
"pacote": "^17.0.4",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"request": "^2.88.2",
|
||||
"request-progress": "^3.0.0"
|
||||
"request-progress": "^3.0.0",
|
||||
"use-debounce": "^9.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^2.1.0",
|
||||
|
||||
1614
node_modules/.yarn-integrity
generated
vendored
1614
node_modules/.yarn-integrity
generated
vendored
File diff suppressed because it is too large
Load Diff
@ -47,7 +47,7 @@ const AvailableModelCard: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg border-gray-200">
|
||||
<div className="flex justify-between py-4 px-3 gap-[10px]">
|
||||
<div className="flex justify-between py-4 px-3 gap-2.5">
|
||||
<DownloadModelContent
|
||||
required={required}
|
||||
author={product.author}
|
||||
|
||||
@ -11,7 +11,6 @@ export default function renderChatMessage({
|
||||
senderName,
|
||||
createdAt,
|
||||
imageUrls,
|
||||
htmlText,
|
||||
text,
|
||||
}: ChatMessage): React.ReactNode {
|
||||
switch (messageType) {
|
||||
@ -45,7 +44,7 @@ export default function renderChatMessage({
|
||||
senderName={senderName}
|
||||
createdAt={createdAt}
|
||||
senderType={messageSenderType}
|
||||
text={htmlText && htmlText.trim().length > 0 ? htmlText : text}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
|
||||
@ -32,7 +32,7 @@ const ConversationalCard: React.FC<Props> = ({ product }) => {
|
||||
{description}
|
||||
</span>
|
||||
</div>
|
||||
<span className="flex text-xs leading-5 text-gray-500 items-center gap-[2px]">
|
||||
<span className="flex text-xs leading-5 text-gray-500 items-center gap-0.5">
|
||||
<Image src={"icons/play.svg"} width={16} height={16} alt="" />
|
||||
32.2k runs
|
||||
</span>
|
||||
|
||||
@ -18,19 +18,19 @@ const DownloadModelContent: React.FC<Props> = ({
|
||||
type,
|
||||
}) => {
|
||||
return (
|
||||
<div className="w-4/5 flex flex-col gap-[10px]">
|
||||
<div className="w-4/5 flex flex-col gap-2.5">
|
||||
<div className="flex items-center gap-1">
|
||||
<h2 className="font-medium text-xl leading-[25px] tracking-[-0.4px] text-gray-900">
|
||||
{name}
|
||||
</h2>
|
||||
<DownloadModelTitle title={type} />
|
||||
<div className="py-[2px] px-[10px] bg-purple-100 rounded-md text-center">
|
||||
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
|
||||
<span className="text-xs leading-[18px] font-semibold text-purple-800">
|
||||
{author}
|
||||
</span>
|
||||
</div>
|
||||
{required && (
|
||||
<div className="py-[2px] px-[10px] bg-purple-100 rounded-md text-center">
|
||||
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
|
||||
<span className="text-xs leading-[18px] text-[#11192899]">
|
||||
Required{" "}
|
||||
</span>
|
||||
@ -44,7 +44,7 @@ const DownloadModelContent: React.FC<Props> = ({
|
||||
<div
|
||||
className={`${
|
||||
isRecommend ? "flex" : "hidden"
|
||||
} w-fit justify-center items-center bg-green-50 rounded-full px-[10px] py-[2px] gap-2`}
|
||||
} w-fit justify-center items-center bg-green-50 rounded-full px-2.5 py-0.5 gap-2`}
|
||||
>
|
||||
<div className="w-3 h-3 rounded-full bg-green-400"></div>
|
||||
<span className="text-green-600 font-medium text-xs leading-18px">
|
||||
|
||||
@ -3,7 +3,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const DownloadModelTitle: React.FC<Props> = ({ title }) => (
|
||||
<div className="py-[2px] px-[10px] bg-purple-100 rounded-md text-center">
|
||||
<div className="py-0.5 px-2.5 bg-purple-100 rounded-md text-center">
|
||||
<span className="text-xs leading-[18px] font-medium text-purple-800">
|
||||
{title}
|
||||
</span>
|
||||
|
||||
@ -16,7 +16,7 @@ const DownloadedModelCard: React.FC<Props> = ({
|
||||
onDeleteClick,
|
||||
}) => (
|
||||
<div className="border rounded-lg border-gray-200">
|
||||
<div className="flex justify-between py-4 px-3 gap-[10px]">
|
||||
<div className="flex justify-between py-4 px-3 gap-2.5">
|
||||
<DownloadModelContent
|
||||
required={required}
|
||||
author={product.author}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { Fragment } from "react";
|
||||
import React from "react";
|
||||
import SearchBar from "../SearchBar";
|
||||
import ModelTable from "../ModelTable";
|
||||
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
@ -6,6 +6,8 @@ import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
const DownloadedModelTable: React.FC = () => {
|
||||
const { downloadedModels } = useGetDownloadedModels();
|
||||
|
||||
if (!downloadedModels || downloadedModels.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="pl-[63px] pr-[89px]">
|
||||
<h3 className="text-xl leading-[25px] mt-[50px]">Downloaded Models</h3>
|
||||
|
||||
@ -8,7 +8,7 @@ type Props = {
|
||||
|
||||
const ExpandableHeader: React.FC<Props> = ({ title, expanded, onClick }) => (
|
||||
<button onClick={onClick} className="flex items-center justify-between px-2">
|
||||
<h2 className="text-gray-400 font-bold text-[12px] leading-[12px] pl-1">
|
||||
<h2 className="text-gray-400 font-bold text-xs leading-[12px] pl-1">
|
||||
{title}
|
||||
</h2>
|
||||
<div className="mr-2">
|
||||
|
||||
@ -1,19 +1,7 @@
|
||||
import HeaderTitle from "../HeaderTitle";
|
||||
import SearchBar, { SearchType } from "../SearchBar";
|
||||
import SimpleCheckbox from "../SimpleCheckbox";
|
||||
import SimpleTag, { TagType } from "../SimpleTag";
|
||||
import ExploreModelList from "../ExploreModelList";
|
||||
|
||||
const tags = [
|
||||
"Roleplay",
|
||||
"Llama",
|
||||
"Story",
|
||||
"Casual",
|
||||
"Professional",
|
||||
"CodeLlama",
|
||||
"Coding",
|
||||
];
|
||||
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
|
||||
import ExploreModelFilter from "../ExploreModelFilter";
|
||||
|
||||
const ExploreModelContainer: React.FC = () => (
|
||||
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
|
||||
@ -23,21 +11,7 @@ const ExploreModelContainer: React.FC = () => (
|
||||
placeholder="Owner name like TheBloke, etc.."
|
||||
/>
|
||||
<div className="flex flex-1 gap-x-10 mt-9 overflow-hidden">
|
||||
<div className="w-64">
|
||||
<h2 className="font-semibold text-xs mb-[15px]">Tags</h2>
|
||||
<SearchBar placeholder="Filter by tags" />
|
||||
<div className="flex flex-wrap gap-[9px] mt-[14px]">
|
||||
{tags.map((item) => (
|
||||
<SimpleTag key={item} title={item} type={item as TagType} />
|
||||
))}
|
||||
</div>
|
||||
<hr className="my-10" />
|
||||
<fieldset>
|
||||
{checkboxs.map((item) => (
|
||||
<SimpleCheckbox key={item} name={item} />
|
||||
))}
|
||||
</fieldset>
|
||||
</div>
|
||||
<ExploreModelFilter />
|
||||
<ExploreModelList />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
40
web/app/_components/ExploreModelFilter/index.tsx
Normal file
40
web/app/_components/ExploreModelFilter/index.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import SearchBar from "../SearchBar";
|
||||
import SimpleCheckbox from "../SimpleCheckbox";
|
||||
import SimpleTag, { TagType } from "../SimpleTag";
|
||||
|
||||
const tags = [
|
||||
"Roleplay",
|
||||
"Llama",
|
||||
"Story",
|
||||
"Casual",
|
||||
"Professional",
|
||||
"CodeLlama",
|
||||
"Coding",
|
||||
];
|
||||
const checkboxs = ["GGUF", "TensorRT", "Meow", "JigglyPuff"];
|
||||
|
||||
const ExploreModelFilter: React.FC = () => {
|
||||
const enabled = false;
|
||||
if (!enabled) return null;
|
||||
|
||||
return (
|
||||
<div className="w-64">
|
||||
<h2 className="font-semibold text-xs mb-[15px]">Tags</h2>
|
||||
<SearchBar placeholder="Filter by tags" />
|
||||
<div className="flex flex-wrap gap-[9px] mt-[14px]">
|
||||
{tags.map((item) => (
|
||||
<SimpleTag key={item} title={item} type={item as TagType} />
|
||||
))}
|
||||
</div>
|
||||
<hr className="my-10" />
|
||||
<fieldset>
|
||||
{checkboxs.map((item) => (
|
||||
<SimpleCheckbox key={item} name={item} />
|
||||
))}
|
||||
</fieldset>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExploreModelFilter;
|
||||
@ -1,3 +1,5 @@
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
"use client";
|
||||
|
||||
import ExploreModelItemHeader from "../ExploreModelItemHeader";
|
||||
@ -11,9 +13,7 @@ type Props = {
|
||||
model: Product;
|
||||
};
|
||||
|
||||
export type Ref = HTMLDivElement;
|
||||
|
||||
const ExploreModelItem = forwardRef<Ref, Props>(({ model }, ref) => {
|
||||
const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
return (
|
||||
@ -33,7 +33,7 @@ const ExploreModelItem = forwardRef<Ref, Props>(({ model }, ref) => {
|
||||
<div className="text-sm font-medium text-gray-500">
|
||||
Model Format
|
||||
</div>
|
||||
<div className="px-[10px] py-0.5 bg-gray-100 text-xs text-gray-800 w-fit">
|
||||
<div className="px-2.5 py-0.5 bg-gray-100 text-xs text-gray-800 w-fit">
|
||||
GGUF
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -66,7 +66,7 @@ const HistoryItem: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`flex flex-row mx-1 items-center gap-[10px] rounded-lg p-2 ${backgroundColor} hover:bg-hover-light`}
|
||||
className={`flex flex-row mx-1 items-center gap-2.5 rounded-lg p-2 ${backgroundColor} hover:bg-hover-light`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Image
|
||||
@ -79,7 +79,7 @@ const HistoryItem: React.FC<Props> = ({
|
||||
<div className="flex flex-col justify-between text-sm leading-[20px] w-full">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<span className="text-gray-900 text-left">{name}</span>
|
||||
<span className="text-[11px] leading-[13px] tracking-[-0.4px] text-gray-400">
|
||||
<span className="text-xs leading-[13px] tracking-[-0.4px] text-gray-400">
|
||||
{updatedAt && new Date(updatedAt).toDateString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -7,7 +7,7 @@ const JanLogo: React.FC = () => {
|
||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom);
|
||||
return (
|
||||
<button
|
||||
className="p-3 flex gap-[2px] items-center"
|
||||
className="p-3 flex gap-0.5 items-center"
|
||||
onClick={() => setActiveConvoId(undefined)}
|
||||
>
|
||||
<Image src={"icons/app_icon.svg"} width={28} height={28} alt="" />
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
import EmptyChatContainer from "../EmptyChatContainer";
|
||||
import MainChat from "../MainChat";
|
||||
|
||||
export default function ChatContainer() {
|
||||
const MainView: React.FC = () => {
|
||||
const viewState = useAtomValue(getMainViewStateAtom);
|
||||
|
||||
switch (viewState) {
|
||||
@ -30,4 +30,6 @@ export default function ChatContainer() {
|
||||
default:
|
||||
return <MainChat />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default MainView;
|
||||
@ -38,7 +38,7 @@ const ModelActionButton: React.FC<Props> = ({ type, onActionClick }) => {
|
||||
|
||||
return (
|
||||
<td className="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<PrimaryButton title={styles.title} onClick={onClick} />
|
||||
<PrimaryButton title={styles.title} onClick={onClick} className={styles.backgroundColor} />
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
@ -6,30 +6,37 @@ type Props = {
|
||||
onDeleteClick: () => void;
|
||||
};
|
||||
|
||||
const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => {
|
||||
return (
|
||||
<Menu as="div" className="relative flex-none">
|
||||
<Menu.Button className="block text-gray-500 hover:text-gray-900">
|
||||
<span className="sr-only">Open options</span>
|
||||
<EllipsisVerticalIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-50 mt-2 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none">
|
||||
<Menu.Item>
|
||||
<button onClick={onDeleteClick}>Delete</button>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
const ModelActionMenu: React.FC<Props> = ({ onDeleteClick }) => (
|
||||
<Menu as="div" className="relative flex-none">
|
||||
<Menu.Button className="block text-gray-500 hover:text-gray-900">
|
||||
<span className="sr-only">Open options</span>
|
||||
<EllipsisVerticalIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-50 mt-2 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 focus:outline-none">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
className={`${
|
||||
active ? "bg-violet-500 text-white" : "text-gray-900"
|
||||
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
||||
onClick={onDeleteClick}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
export default ModelActionMenu;
|
||||
|
||||
@ -11,7 +11,7 @@ const ModelDownloadingButton: React.FC<Props> = ({ total, value }) => {
|
||||
<button className="py-2 px-3 flex gap-2 border text-xs leading-[18px] border-gray-200 rounded-lg">
|
||||
Downloading...
|
||||
</button>
|
||||
<div className="py-[2px] px-[10px] bg-gray-200 rounded">
|
||||
<div className="py-0.5 px-2.5 bg-gray-200 rounded">
|
||||
<span className="text-xs font-medium text-gray-800">
|
||||
{toGigabytes(value)} / {toGigabytes(total)}
|
||||
</span>
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useAtomValue } from "jotai";
|
||||
import { searchingModelText } from "@/_helpers/JotaiWrapper";
|
||||
import { Product } from "@/_models/Product";
|
||||
import DownloadedModelCard from "../DownloadedModelCard";
|
||||
import AvailableModelCard from "../AvailableModelCard";
|
||||
import useDeleteModel from "@/_hooks/useDeleteModel";
|
||||
import useGetAvailableModels from "@/_hooks/useGetAvailableModels";
|
||||
import useDownloadModel from "@/_hooks/useDownloadModel";
|
||||
|
||||
const ModelListContainer: React.FC = () => {
|
||||
const searchText = useAtomValue(searchingModelText);
|
||||
const { deleteModel } = useDeleteModel();
|
||||
const { downloadModel } = useDownloadModel();
|
||||
|
||||
const {
|
||||
availableModels,
|
||||
downloadedModels,
|
||||
getAvailableModelExceptDownloaded,
|
||||
} = useGetAvailableModels();
|
||||
|
||||
const onDeleteClick = async (product: Product) => {
|
||||
await deleteModel(product);
|
||||
await getAvailableModelExceptDownloaded();
|
||||
};
|
||||
|
||||
const onDownloadClick = async (model: Product) => {
|
||||
await downloadModel(model);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full pl-[63px] pr-[89px] pt-[60px] overflow-y-auto">
|
||||
<div className="pb-5 flex flex-col gap-2">
|
||||
<Title title="Downloaded models" />
|
||||
{downloadedModels
|
||||
?.filter(
|
||||
(e) =>
|
||||
searchText.toLowerCase().trim() === "" ||
|
||||
e.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
.map((item) => (
|
||||
<DownloadedModelCard
|
||||
key={item.id}
|
||||
product={item}
|
||||
onDeleteClick={onDeleteClick}
|
||||
isRecommend={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="pb-5 flex flex-col gap-2">
|
||||
<Title title="Browse available models" />
|
||||
{availableModels
|
||||
?.filter(
|
||||
(e) =>
|
||||
searchText.toLowerCase().trim() === "" ||
|
||||
e.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
.map((item) => (
|
||||
<AvailableModelCard
|
||||
key={item.id}
|
||||
product={item}
|
||||
onDownloadClick={onDownloadClick}
|
||||
isRecommend={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
const Title: React.FC<Props> = ({ title }) => {
|
||||
return (
|
||||
<div className="flex gap-[10px]">
|
||||
<span className="font-semibold text-xl leading-[25px] tracking-[-0.4px]">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelListContainer;
|
||||
@ -1,15 +0,0 @@
|
||||
import HeaderBackButton from "../HeaderBackButton";
|
||||
import HeaderTitle from "../HeaderTitle";
|
||||
import ModelListContainer from "../ModelListContainer";
|
||||
import ModelSearchBar from "../ModelSearchBar";
|
||||
|
||||
export default function ModelManagement() {
|
||||
return (
|
||||
<main className="pt-[30px] pr-[89px] pl-[60px] pb-[70px] flex-1">
|
||||
{/* <HeaderBackButton /> */}
|
||||
<HeaderTitle title="Explore Models" />
|
||||
<ModelSearchBar />
|
||||
<ModelListContainer />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@ -1,17 +1,17 @@
|
||||
import { Fragment, useEffect } from "react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid";
|
||||
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import { Product } from "@/_models/Product";
|
||||
import { useAtom } from "jotai";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { selectedModelAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import { downloadedModelAtom } from "@/_helpers/atoms/DownloadedModel.atom";
|
||||
|
||||
function classNames(...classes: any) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
const SelectModels: React.FC = () => {
|
||||
const { downloadedModels } = useGetDownloadedModels();
|
||||
const downloadedModels = useAtomValue(downloadedModelAtom);
|
||||
const [selectedModel, setSelectedModel] = useAtom(selectedModelAtom);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -38,7 +38,7 @@ export const ModelStatusComponent: React.FC<Props> = ({ status }) => {
|
||||
const statusType = ModelStatusMapper[status];
|
||||
return (
|
||||
<div
|
||||
className={`rounded-[10px] py-0.5 px-[10px] w-fit text-xs font-medium ${statusType.backgroundColor}`}
|
||||
className={`rounded-[10px] py-0.5 px-2.5 w-fit text-xs font-medium ${statusType.backgroundColor}`}
|
||||
>
|
||||
{statusType.title}
|
||||
</div>
|
||||
|
||||
@ -2,63 +2,43 @@ import ProgressBar from "../ProgressBar";
|
||||
import SystemItem from "../SystemItem";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { appDownloadProgress } from "@/_helpers/JotaiWrapper";
|
||||
import { useEffect, useState } from "react";
|
||||
import { executeSerial } from "../../../../electron/core/plugin-manager/execution/extension-manager";
|
||||
import { SystemMonitoringService } from "../../../shared/coreService";
|
||||
import { getSystemBarVisibilityAtom } from "@/_helpers/atoms/SystemBar.atom";
|
||||
import { currentProductAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import useGetAppVersion from "@/_hooks/useGetAppVersion";
|
||||
import useGetSystemResources from "@/_hooks/useGetSystemResources";
|
||||
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
|
||||
import { DownloadState } from "@/_models/DownloadState";
|
||||
import { formatDownloadPercentage } from "@/_utils/converter";
|
||||
|
||||
const MonitorBar: React.FC = () => {
|
||||
const show = useAtomValue(getSystemBarVisibilityAtom);
|
||||
const progress = useAtomValue(appDownloadProgress);
|
||||
const activeModel = useAtomValue(currentProductAtom);
|
||||
const [ram, setRam] = useState<number>(0);
|
||||
const [gpu, setGPU] = useState<number>(0);
|
||||
const [cpu, setCPU] = useState<number>(0);
|
||||
const [version, setVersion] = useState<string>("");
|
||||
const { version } = useGetAppVersion();
|
||||
const { ram, cpu } = useGetSystemResources();
|
||||
const modelDownloadStates = useAtomValue(modelDownloadStateAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const getSystemResources = async () => {
|
||||
const resourceInfor = await executeSerial(
|
||||
SystemMonitoringService.GET_RESOURCES_INFORMATION
|
||||
);
|
||||
const currentLoadInfor = await executeSerial(
|
||||
SystemMonitoringService.GET_CURRENT_LOAD_INFORMATION
|
||||
);
|
||||
const ram =
|
||||
(resourceInfor?.mem?.used ?? 0) / (resourceInfor?.mem?.total ?? 1);
|
||||
setRam(Math.round(ram * 100));
|
||||
setCPU(Math.round(currentLoadInfor?.currentLoad ?? 0));
|
||||
};
|
||||
const getAppVersion = () => {
|
||||
window.electronAPI.appVersion().then((version: string | undefined) => {
|
||||
setVersion(version ?? "");
|
||||
});
|
||||
};
|
||||
getAppVersion();
|
||||
getSystemResources();
|
||||
// Fetch interval - every 3s
|
||||
const intervalId = setInterval(() => {
|
||||
getSystemResources();
|
||||
}, 3000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
if (!show) return null;
|
||||
const downloadStates: DownloadState[] = [];
|
||||
for (const [, value] of Object.entries(modelDownloadStates)) {
|
||||
downloadStates.push(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center justify-between border-t border-gray-200">
|
||||
{progress && progress >= 0 ? (
|
||||
<ProgressBar total={100} used={progress} />
|
||||
) : (
|
||||
<div className="w-full" />
|
||||
)}
|
||||
<div className="flex-1 flex items-center gap-8 px-2">
|
||||
) : null}
|
||||
<div className="flex-1 justify-end flex items-center gap-8 px-2">
|
||||
{downloadStates.length > 0 && (
|
||||
<SystemItem
|
||||
name="Downloading"
|
||||
value={`${downloadStates[0].fileName}: ${formatDownloadPercentage(
|
||||
downloadStates[0].percent
|
||||
)}`}
|
||||
/>
|
||||
)}
|
||||
<SystemItem name="CPU" value={`${cpu}%`} />
|
||||
<SystemItem name="Mem" value={`${ram}%`} />
|
||||
|
||||
{activeModel && (
|
||||
<SystemItem name={`Active model: ${activeModel.name}`} value={"1"} />
|
||||
<SystemItem name={`Active model: ${activeModel.name}`} value={""} />
|
||||
)}
|
||||
<span className="text-gray-900 text-sm">v{version}</span>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@ import ActiveModelTable from "../ActiveModelTable";
|
||||
import DownloadingModelTable from "../DownloadingModelTable";
|
||||
|
||||
const MyModelContainer: React.FC = () => (
|
||||
<div className="flex flex-col w-full h-full pt-[60px]">
|
||||
<div className="flex flex-col flex-1 pt-[60px]">
|
||||
<HeaderTitle title="My Models" className="pl-[63px] pr-[89px]" />
|
||||
<div className="pb-6 overflow-y-auto scroll">
|
||||
<ActiveModelTable />
|
||||
|
||||
@ -4,17 +4,19 @@ type Props = {
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
fullWidth?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const PrimaryButton: React.FC<Props> = ({
|
||||
title,
|
||||
onClick,
|
||||
fullWidth = false,
|
||||
className,
|
||||
}) => (
|
||||
<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 line-clamp-1 flex-shrink-0 ${
|
||||
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 ${className} ${
|
||||
fullWidth ? "flex-1 " : ""
|
||||
}}`}
|
||||
>
|
||||
|
||||
@ -5,24 +5,22 @@ type Props = {
|
||||
used: number;
|
||||
};
|
||||
|
||||
const ProgressBar: React.FC<Props> = ({ used, total }) => {
|
||||
return (
|
||||
<div className="flex gap-[10px] items-center p-[10px]">
|
||||
<div className="text-xs leading-[18px] gap-0.5 flex items-center">
|
||||
<Image src={"icons/app_icon.svg"} width={18} height={18} alt="" />
|
||||
Updating
|
||||
</div>
|
||||
<div className="w-[150px] relative bg-blue-200 h-1 rounded-md flex">
|
||||
<div
|
||||
className="absolute top-0 left-0 h-full rounded-md bg-blue-600"
|
||||
style={{ width: `${((used / total) * 100).toFixed(2)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs leading-[18px]">
|
||||
{((used / total) * 100).toFixed(0)}%
|
||||
</div>
|
||||
const ProgressBar: React.FC<Props> = ({ used, total }) => (
|
||||
<div className="flex gap-2.5 items-center p-[10px]">
|
||||
<div className="text-xs leading-[18px] gap-0.5 flex items-center">
|
||||
<Image src={"icons/app_icon.svg"} width={18} height={18} alt="" />
|
||||
Updating
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<div className="w-[150px] relative bg-blue-200 h-1 rounded-md flex">
|
||||
<div
|
||||
className="absolute top-0 left-0 h-full rounded-md bg-blue-600"
|
||||
style={{ width: `${((used / total) * 100).toFixed(2)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs leading-[18px]">
|
||||
{((used / total) * 100).toFixed(0)}%
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ProgressBar;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import ChatContainer from "../ChatContainer";
|
||||
import MainView from "../MainView";
|
||||
import MonitorBar from "../MonitorBar";
|
||||
|
||||
const RightContainer = () => (
|
||||
<div className="flex flex-col flex-1 h-screen">
|
||||
<ChatContainer />
|
||||
<MainView />
|
||||
<MonitorBar />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { modelSearchAtom } from "@/_helpers/JotaiWrapper";
|
||||
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useState } from "react";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
|
||||
export enum SearchType {
|
||||
Model = "model",
|
||||
@ -13,21 +13,12 @@ type Props = {
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
const debounced = useDebouncedCallback((value) => {
|
||||
setModelSearch(value);
|
||||
}, 300);
|
||||
|
||||
return (
|
||||
<div className="relative mt-3 flex items-center">
|
||||
@ -43,10 +34,8 @@ const SearchBar: React.FC<Props> = ({ type, placeholder }) => {
|
||||
type="text"
|
||||
name="search"
|
||||
id="search"
|
||||
value={searchValue}
|
||||
placeholder={placeholderText}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={(e) => debounced(e.target.value)}
|
||||
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>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
|
||||
import { currentConvoStateAtom } from "@/_helpers/atoms/Conversation.atom";
|
||||
import useSendChatMessage from "@/_hooks/useSendChatMessage";
|
||||
import { ArrowRightIcon } from "@heroicons/react/24/outline";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import Image from "next/image";
|
||||
|
||||
const SendButton: React.FC = () => {
|
||||
const [currentPrompt] = useAtom(currentPromptAtom);
|
||||
@ -25,9 +25,9 @@ const SendButton: React.FC = () => {
|
||||
onClick={sendChatMessage}
|
||||
style={disabled ? disabledStyle : enabledStyle}
|
||||
type="submit"
|
||||
className="p-2 gap-[10px] inline-flex items-center rounded-[12px] text-sm font-semibold shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||
className="p-2 gap-2.5 inline-flex items-center rounded-xl text-sm font-semibold shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||
>
|
||||
<Image src={"icons/ic_arrowright.svg"} width={24} height={24} alt="" />
|
||||
<ArrowRightIcon width={16} height={16} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,7 +2,6 @@ import Image from "next/image";
|
||||
import useCreateConversation from "@/_hooks/useCreateConversation";
|
||||
import PrimaryButton from "../PrimaryButton";
|
||||
import { useAtomValue, useSetAtom } from "jotai";
|
||||
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
MainViewState,
|
||||
@ -11,6 +10,7 @@ import {
|
||||
import { currentProductAtom } from "@/_helpers/atoms/Model.atom";
|
||||
import useInitModel from "@/_hooks/useInitModel";
|
||||
import { Product } from "@/_models/Product";
|
||||
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
|
||||
enum ActionButton {
|
||||
DownloadModel = "Download a Model",
|
||||
|
||||
@ -2,7 +2,7 @@ import React from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
const SidebarHeader: React.FC = () => (
|
||||
<div className="flex flex-col gap-[10px] px-3">
|
||||
<div className="flex flex-col gap-2.5 px-3">
|
||||
<Image src={"icons/Jan_AppIcon.svg"} width={68} height={28} alt="" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -30,15 +30,15 @@ const SimpleControlNetMessage: React.FC<Props> = ({
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1 justify-start items-baseline">
|
||||
<div className="text-[#1B1B1B] text-[13px] font-extrabold leading-[15.2px]">
|
||||
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="text-[11px] leading-[13.2px] font-medium text-gray-400 ml-2">
|
||||
<div className="text-xs leading-[13.2px] font-medium text-gray-400 ml-2">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3 flex-col">
|
||||
<p className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]">
|
||||
<p className="leading-[20px] whitespace-break-spaces text-sm font-normal dark:text-[#d1d5db]">
|
||||
{text}
|
||||
</p>
|
||||
<JanImage
|
||||
@ -49,7 +49,7 @@ const SimpleControlNetMessage: React.FC<Props> = ({
|
||||
<Link
|
||||
href={imageUrls[0] || "#"}
|
||||
target="_blank_"
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-[12px]"
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
|
||||
>
|
||||
<Image src="icons/download.svg" width={16} height={16} alt="" />
|
||||
<span className="leading-[20px] text-[14px] text-[#111928]">
|
||||
|
||||
@ -30,10 +30,10 @@ const SimpleImageMessage: React.FC<Props> = ({
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1 justify-start items-baseline">
|
||||
<div className="text-[#1B1B1B] text-[13px] font-extrabold leading-[15.2px]">
|
||||
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="text-[11px] leading-[13.2px] font-medium text-gray-400 ml-2">
|
||||
<div className="text-xs leading-[13.2px] font-medium text-gray-400 ml-2">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
@ -46,19 +46,19 @@ const SimpleImageMessage: React.FC<Props> = ({
|
||||
<Link
|
||||
href={imageUrls[0] || "#"}
|
||||
target="_blank_"
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-[12px]"
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
|
||||
>
|
||||
<Image src="icons/download.svg" width={16} height={16} alt="" />
|
||||
<span className="leading-[20px] text-[14px] text-[#111928]">
|
||||
<span className="leading-[20px] text-sm text-[#111928]">
|
||||
Download
|
||||
</span>
|
||||
</Link>
|
||||
<button
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-[12px]"
|
||||
className="flex gap-1 items-center px-2 py-1 bg-[#F3F4F6] rounded-xl"
|
||||
// onClick={() => sendChatMessage()}
|
||||
>
|
||||
<Image src="icons/refresh.svg" width={16} height={16} alt="" />
|
||||
<span className="leading-[20px] text-[14px] text-[#111928]">
|
||||
<span className="leading-[20px] text-sm text-[#111928]">
|
||||
Re-generate
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@ -69,7 +69,7 @@ const SimpleTag: React.FC<Props> = ({
|
||||
if (!clickable) {
|
||||
return (
|
||||
<div
|
||||
className={`px-[10px] py-0.5 rounded text-xs font-medium ${tagStyleMapper[type]}`}
|
||||
className={`px-2.5 py-0.5 rounded text-xs font-medium ${tagStyleMapper[type]}`}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
@ -79,7 +79,7 @@ const SimpleTag: React.FC<Props> = ({
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`px-[10px] py-0.5 rounded text-xs font-medium ${tagStyleMapper[type]}`}
|
||||
className={`px-2.5 py-0.5 rounded text-xs font-medium ${tagStyleMapper[type]}`}
|
||||
>
|
||||
{title} x
|
||||
</button>
|
||||
|
||||
@ -3,54 +3,61 @@ import { displayDate } from "@/_utils/datetime";
|
||||
import { TextCode } from "../TextCode";
|
||||
import { getMessageCode } from "@/_utils/message";
|
||||
import Image from "next/image";
|
||||
import { MessageSenderType } from "@/_models/ChatMessage";
|
||||
|
||||
type Props = {
|
||||
avatarUrl: string;
|
||||
senderName: string;
|
||||
createdAt: number;
|
||||
senderType: MessageSenderType;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
const SimpleTextMessage: React.FC<Props> = ({
|
||||
senderName,
|
||||
createdAt,
|
||||
senderType,
|
||||
avatarUrl = "",
|
||||
text = "",
|
||||
}) => (
|
||||
<div className="flex items-start gap-2 ml-3">
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src={avatarUrl}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<div className="flex gap-1 justify-start items-baseline">
|
||||
<div className="text-[#1B1B1B] text-[13px] font-extrabold leading-[15.2px] dark:text-[#d1d5db]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="text-[11px] leading-[13.2px] font-medium text-gray-400">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
{text.includes("```") ? (
|
||||
getMessageCode(text).map((item, i) => (
|
||||
<div className="flex gap-1 flex-col" key={i}>
|
||||
<p className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]">
|
||||
{item.text}
|
||||
</p>
|
||||
{item.code.trim().length > 0 && <TextCode text={item.code} />}
|
||||
}) => {
|
||||
const backgroundColor =
|
||||
senderType === MessageSenderType.User ? "" : "bg-gray-100";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-start gap-2 px-[148px] ${backgroundColor} py-5`}
|
||||
>
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src={avatarUrl}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex gap-1 justify-start items-baseline">
|
||||
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px] dark:text-[#d1d5db]">
|
||||
{senderName}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p
|
||||
className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]"
|
||||
dangerouslySetInnerHTML={{ __html: text }}
|
||||
/>
|
||||
)}
|
||||
<div className="text-xs leading-[13.2px] font-medium text-gray-400">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
{text.includes("```") ? (
|
||||
getMessageCode(text).map((item, i) => (
|
||||
<div className="flex gap-1 flex-col" key={i}>
|
||||
<p className="leading-[20px] whitespace-break-spaces text-sm font-normal dark:text-[#d1d5db]">
|
||||
{item.text}
|
||||
</p>
|
||||
{item.code.trim().length > 0 && <TextCode text={item.code} />}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<span className="text-sm">{text}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SimpleTextMessage);
|
||||
|
||||
@ -34,17 +34,17 @@ const StreamTextMessage: React.FC<Props> = ({
|
||||
/>
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<div className="flex gap-1 justify-start items-baseline">
|
||||
<div className="text-[#1B1B1B] text-[13px] font-extrabold leading-[15.2px] dark:text-[#d1d5db]">
|
||||
<div className="text-[#1B1B1B] text-sm font-extrabold leading-[15.2px] dark:text-[#d1d5db]">
|
||||
{senderName}
|
||||
</div>
|
||||
<div className="text-[11px] leading-[13.2px] font-medium text-gray-400">
|
||||
<div className="text-xs leading-[13.2px] font-medium text-gray-400">
|
||||
{displayDate(createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
{message.text.includes("```") ? (
|
||||
getMessageCode(message.text).map((item, i) => (
|
||||
<div className="flex gap-1 flex-col" key={i}>
|
||||
<p className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]">
|
||||
<p className="leading-[20px] whitespace-break-spaces text-sm font-normal dark:text-[#d1d5db]">
|
||||
{item.text}
|
||||
</p>
|
||||
{item.code.trim().length > 0 && <TextCode text={item.code} />}
|
||||
@ -52,7 +52,7 @@ const StreamTextMessage: React.FC<Props> = ({
|
||||
))
|
||||
) : (
|
||||
<p
|
||||
className="leading-[20px] whitespace-break-spaces text-[14px] font-normal dark:text-[#d1d5db]"
|
||||
className="leading-[20px] whitespace-break-spaces text-sm font-normal dark:text-[#d1d5db]"
|
||||
dangerouslySetInnerHTML={{ __html: message.text }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -3,15 +3,13 @@ type Props = {
|
||||
value: string;
|
||||
};
|
||||
|
||||
const SystemItem: React.FC<Props> = ({ name, value }) => {
|
||||
return (
|
||||
<div className="flex gap-2 pl-4 my-1">
|
||||
<div className="flex gap-[10px] w-max font-bold text-gray-900 text-sm">
|
||||
{name}
|
||||
</div>
|
||||
<span className="text-gray-900 text-sm">{value}</span>
|
||||
const SystemItem: React.FC<Props> = ({ name, value }) => (
|
||||
<div className="flex gap-2 pl-4 my-1">
|
||||
<div className="flex gap-2.5 w-max font-bold text-gray-900 text-sm">
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<span className="text-gray-900 text-sm">{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SystemItem;
|
||||
|
||||
@ -17,7 +17,7 @@ export const TabModelDetail: React.FC<Props> = ({ onTabClick, tab }) => {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex gap-[2px] rounded p-1 w-full bg-gray-200">
|
||||
<div className="flex gap-0.5 rounded p-1 w-full bg-gray-200">
|
||||
{btns.map((item, index) => (
|
||||
<button
|
||||
key={index}
|
||||
|
||||
@ -19,7 +19,7 @@ const UserToolbar: React.FC = () => {
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
<span className="flex gap-[2px] leading-6 text-base font-semibold">
|
||||
<span className="flex gap-0.5 leading-6 text-base font-semibold">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -9,7 +9,7 @@ const ViewModelDetailButton: React.FC<Props> = ({ callback }) => {
|
||||
<div className="px-4 pb-4">
|
||||
<button
|
||||
onClick={callback}
|
||||
className="bg-gray-100 py-1 px-[10px] w-full flex items-center justify-center gap-1 rounded-lg"
|
||||
className="bg-gray-100 py-1 px-2.5 w-full flex items-center justify-center gap-1 rounded-lg"
|
||||
>
|
||||
<span className="text-xs leading-[18px]">View Details</span>
|
||||
<ChevronDownIcon width={18} height={18} />
|
||||
|
||||
@ -4,12 +4,14 @@ import { useSetAtom } from "jotai";
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import { appDownloadProgress } from "./JotaiWrapper";
|
||||
import { DownloadState } from "@/_models/DownloadState";
|
||||
import { execute } from "../../../electron/core/plugin-manager/execution/extension-manager";
|
||||
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
|
||||
import { DataService } from "../../shared/coreService";
|
||||
import {
|
||||
setDownloadStateAtom,
|
||||
setDownloadStateSuccessAtom,
|
||||
} from "./atoms/DownloadState.atom";
|
||||
import { getDownloadedModels } from "@/_hooks/useGetDownloadedModels";
|
||||
import { downloadedModelAtom } from "./atoms/DownloadedModel.atom";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
@ -19,6 +21,7 @@ export default function EventListenerWrapper({ children }: Props) {
|
||||
const setDownloadState = useSetAtom(setDownloadStateAtom);
|
||||
const setDownloadStateSuccess = useSetAtom(setDownloadStateSuccessAtom);
|
||||
const setProgress = useSetAtom(appDownloadProgress);
|
||||
const setDownloadedModels = useSetAtom(downloadedModelAtom);
|
||||
|
||||
useEffect(() => {
|
||||
if (window && window.electronAPI) {
|
||||
@ -39,7 +42,15 @@ export default function EventListenerWrapper({ children }: Props) {
|
||||
(_event: string, callback: any) => {
|
||||
if (callback && callback.fileName) {
|
||||
setDownloadStateSuccess(callback.fileName);
|
||||
execute(DataService.UPDATE_FINISHED_DOWNLOAD, callback.fileName);
|
||||
|
||||
executeSerial(
|
||||
DataService.UPDATE_FINISHED_DOWNLOAD,
|
||||
callback.fileName
|
||||
).then(() => {
|
||||
getDownloadedModels().then((models) => {
|
||||
setDownloadedModels(models);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
4
web/app/_helpers/atoms/DownloadedModel.atom.ts
Normal file
4
web/app/_helpers/atoms/DownloadedModel.atom.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { Product } from "@/_models/Product";
|
||||
import { atom } from "jotai";
|
||||
|
||||
export const downloadedModelAtom = atom<Product[]>([]);
|
||||
@ -1,11 +1,20 @@
|
||||
import { execute, executeSerial } from "@/_services/pluginService";
|
||||
import { DataService, ModelManagementService } from "../../shared/coreService";
|
||||
import { Product } from "@/_models/Product";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { downloadedModelAtom } from "@/_helpers/atoms/DownloadedModel.atom";
|
||||
import { getDownloadedModels } from "./useGetDownloadedModels";
|
||||
|
||||
export default function useDeleteModel() {
|
||||
const setDownloadedModels = useSetAtom(downloadedModelAtom);
|
||||
|
||||
const deleteModel = async (model: Product) => {
|
||||
execute(DataService.DELETE_DOWNLOAD_MODEL, model.id);
|
||||
await executeSerial(ModelManagementService.DELETE_MODEL, model.fileName);
|
||||
|
||||
// reload models
|
||||
const downloadedModels = await getDownloadedModels();
|
||||
setDownloadedModels(downloadedModels);
|
||||
};
|
||||
|
||||
return { deleteModel };
|
||||
|
||||
17
web/app/_hooks/useGetAppVersion.ts
Normal file
17
web/app/_hooks/useGetAppVersion.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function useGetAppVersion() {
|
||||
const [version, setVersion] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
getAppVersion();
|
||||
}, []);
|
||||
|
||||
const getAppVersion = () => {
|
||||
window.electronAPI.appVersion().then((version: string | undefined) => {
|
||||
setVersion(version ?? "");
|
||||
});
|
||||
};
|
||||
|
||||
return { version };
|
||||
}
|
||||
@ -1,11 +1,13 @@
|
||||
import { ModelVersion, Product, ProductType } from "@/_models/Product";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } 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 { useAtom } from "jotai";
|
||||
import { downloadedModelAtom } from "@/_helpers/atoms/DownloadedModel.atom";
|
||||
|
||||
export function useGetDownloadedModels() {
|
||||
const [downloadedModels, setDownloadedModels] = useState<Product[]>([]);
|
||||
const [downloadedModels, setDownloadedModels] = useAtom(downloadedModelAtom);
|
||||
|
||||
useEffect(() => {
|
||||
getDownloadedModels().then((downloadedModels) => {
|
||||
@ -42,11 +44,12 @@ export async function searchHfModels(
|
||||
const modelVersions: ModelVersion[] = [];
|
||||
|
||||
for (const [, file] of Object.entries(model.files)) {
|
||||
const fileData: any = file as any;
|
||||
const modelVersion: ModelVersion = {
|
||||
path: file.path,
|
||||
type: file.type,
|
||||
downloadUrl: file.downloadLink,
|
||||
size: file.size,
|
||||
path: fileData.path,
|
||||
type: fileData.type,
|
||||
downloadUrl: fileData.downloadLink,
|
||||
size: fileData.size,
|
||||
};
|
||||
modelVersions.push(modelVersion);
|
||||
}
|
||||
|
||||
38
web/app/_hooks/useGetSystemResources.ts
Normal file
38
web/app/_hooks/useGetSystemResources.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
|
||||
import { SystemMonitoringService } from "../../shared/coreService";
|
||||
|
||||
export default function useGetSystemResources() {
|
||||
const [ram, setRam] = useState<number>(0);
|
||||
const [cpu, setCPU] = useState<number>(0);
|
||||
|
||||
const getSystemResources = async () => {
|
||||
const resourceInfor = await executeSerial(
|
||||
SystemMonitoringService.GET_RESOURCES_INFORMATION
|
||||
);
|
||||
const currentLoadInfor = await executeSerial(
|
||||
SystemMonitoringService.GET_CURRENT_LOAD_INFORMATION
|
||||
);
|
||||
const ram =
|
||||
(resourceInfor?.mem?.used ?? 0) / (resourceInfor?.mem?.total ?? 1);
|
||||
setRam(Math.round(ram * 100));
|
||||
setCPU(Math.round(currentLoadInfor?.currentLoad ?? 0));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSystemResources();
|
||||
|
||||
// Fetch interval - every 3s
|
||||
const intervalId = setInterval(() => {
|
||||
getSystemResources();
|
||||
}, 3000);
|
||||
|
||||
// clean up
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
ram,
|
||||
cpu,
|
||||
};
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
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;
|
||||
};
|
||||
@ -13,7 +13,7 @@ const Page: React.FC = () => {
|
||||
<div className="relative flex flex-col text-black items-center h-screen overflow-y-scroll scroll pt-2">
|
||||
<div className="absolute top-3 left-5">
|
||||
<Link href="/" className="flex flex-row gap-2">
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<div className="flex gap-0.5 items-center">
|
||||
<Image src={"icons/app_icon.svg"} width={28} height={28} alt="" />
|
||||
<Image src={"icons/Jan.svg"} width={27} height={12} alt="" />
|
||||
</div>
|
||||
|
||||
@ -13,7 +13,7 @@ const Page: React.FC = () => {
|
||||
<div className="flex flex-col text-black items-center h-screen overflow-y-scroll scroll pt-2">
|
||||
<div className="absolute top-3 left-5">
|
||||
<Link href="/" className="flex flex-row gap-2">
|
||||
<div className="flex gap-[2px] items-center">
|
||||
<div className="flex gap-0.5 items-center">
|
||||
<Image src={"icons/app_icon.svg"} width={28} height={28} alt="" />
|
||||
<Image src={"icons/Jan.svg"} width={27} height={12} alt="" />
|
||||
</div>
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.92 11.6202C17.8724 11.4974 17.801 11.3853 17.71 11.2902L12.71 6.29019C12.6168 6.19695 12.5061 6.12299 12.3842 6.07253C12.2624 6.02207 12.1319 5.99609 12 5.99609C11.7337 5.99609 11.4783 6.10188 11.29 6.29019C11.1968 6.38342 11.1228 6.49411 11.0723 6.61594C11.0219 6.73776 10.9959 6.86833 10.9959 7.00019C10.9959 7.26649 11.1017 7.52188 11.29 7.71019L14.59 11.0002H7C6.73478 11.0002 6.48043 11.1055 6.29289 11.2931C6.10536 11.4806 6 11.735 6 12.0002C6 12.2654 6.10536 12.5198 6.29289 12.7073C6.48043 12.8948 6.73478 13.0002 7 13.0002H14.59L11.29 16.2902C11.1963 16.3831 11.1219 16.4937 11.0711 16.6156C11.0203 16.7375 10.9942 16.8682 10.9942 17.0002C10.9942 17.1322 11.0203 17.2629 11.0711 17.3848C11.1219 17.5066 11.1963 17.6172 11.29 17.7102C11.383 17.8039 11.4936 17.8783 11.6154 17.9291C11.7373 17.9798 11.868 18.006 12 18.006C12.132 18.006 12.2627 17.9798 12.3846 17.9291C12.5064 17.8783 12.617 17.8039 12.71 17.7102L17.71 12.7102C17.801 12.6151 17.8724 12.5029 17.92 12.3802C18.02 12.1367 18.02 11.8636 17.92 11.6202Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
Loading…
x
Reference in New Issue
Block a user