Signed-off-by: James <james@jan.ai>
This commit is contained in:
James 2023-10-10 18:53:56 -07:00
parent d982dce090
commit 773bbaf4cc
17 changed files with 298 additions and 173 deletions

View File

@ -116,22 +116,10 @@ function storeModel(params: any) {
model.nsfw,
modelTags,
model.greeting,
model.type,
function (err: any) {
if (err) {
// Handle the insertion error here
console.error(err.message);
res(undefined);
return;
}
// @ts-ignoreF
const id = this.lastID;
res(id);
return;
}
model.type
);
stmt.finalize();
// insert modelVersion to MODEL_VERSION_TABLE_INSERTION
const stmt2 = db.prepare(MODEL_VERSION_TABLE_INSERTION);
stmt2.run(
modelVersion.id,
@ -143,25 +131,14 @@ function storeModel(params: any) {
modelVersion.usecase,
modelVersion.downloadLink,
model.id,
modelVersion.startDownloadAt,
function (err: any) {
if (err) {
// Handle the insertion error here
console.error(err.message);
res(undefined);
return;
}
// @ts-ignoreF
const id = this.lastID;
res(id);
return;
}
modelVersion.startDownloadAt
);
stmt.finalize();
stmt2.finalize();
});
db.close();
res(undefined);
});
}
@ -171,7 +148,7 @@ function storeModel(params: any) {
* @param model Product
*/
function updateFinishedDownloadAt(modelVersionId: string) {
return new Promise((res) => {
return new Promise((res, rej) => {
const db = new sqlite3.Database(getDbPath());
const time = Date.now();
console.debug(
@ -181,7 +158,7 @@ function updateFinishedDownloadAt(modelVersionId: string) {
db.run(stmt, [time, modelVersionId], (err: any) => {
if (err) {
console.log(err);
res(undefined);
rej(err);
} else {
console.log("Updated 1 row");
res("Updated");
@ -299,7 +276,7 @@ function deleteDownloadModel(modelId: string) {
});
}
async function fetchModelVersion(db: any, versionId: string) {
function fetchModelVersion(db: any, versionId: string) {
return new Promise((resolve, reject) => {
db.get(
"SELECT * FROM model_versions WHERE id = ?",
@ -308,32 +285,7 @@ async function fetchModelVersion(db: any, versionId: string) {
if (err) {
reject(err);
} else {
if (row) {
const product = {
id: row.id,
slug: row.slug,
name: row.name,
description: row.description,
avatarUrl: row.avatar_url,
longDescription: row.long_description,
technicalDescription: row.technical_description,
author: row.author,
version: row.version,
modelUrl: row.model_url,
nsfw: row.nsfw,
greeting: row.greeting,
type: row.type,
inputs: row.inputs,
outputs: row.outputs,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at),
fileName: row.file_name,
downloadUrl: row.download_url,
};
resolve(product);
} else {
resolve(undefined);
}
resolve(row);
}
}
);

View File

@ -1,6 +1,6 @@
{
"name": "data-plugin",
"version": "1.0.0",
"version": "1.0.1",
"description": "Jan Database Plugin efficiently stores conversation and model data using SQLite, providing accessible data management",
"icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/circle-stack.svg",
"main": "dist/index.js",

View File

@ -1,6 +1,6 @@
const MODULE_PATH = "model-management-plugin/dist/module.js";
const getDownloadedModels = async () =>
const getDownloadedModels = () =>
new Promise(async (resolve) => {
if (window.electronAPI) {
window.electronAPI
@ -9,7 +9,7 @@ const getDownloadedModels = async () =>
}
});
const getAvailableModels = async () =>
const getAvailableModels = () =>
new Promise(async (resolve) => {
if (window.electronAPI) {
window.electronAPI
@ -18,7 +18,7 @@ const getAvailableModels = async () =>
}
});
const downloadModel = async (product) =>
const downloadModel = (product) =>
new Promise(async (resolve) => {
if (window && window.electronAPI) {
window.electronAPI
@ -29,7 +29,7 @@ const downloadModel = async (product) =>
}
});
const deleteModel = async (path) =>
const deleteModel = (path) =>
new Promise(async (resolve) => {
if (window.electronAPI) {
console.debug(`Delete model model management plugin: ${path}`);
@ -38,7 +38,7 @@ const deleteModel = async (path) =>
}
});
const searchModels = async (params) =>
const searchModels = (params) =>
new Promise(async (resolve) => {
if (window.electronAPI) {
window.electronAPI
@ -47,7 +47,7 @@ const searchModels = async (params) =>
}
});
const getConfiguredModels = async () =>
const getConfiguredModels = () =>
new Promise(async (resolve) => {
if (window.electronAPI) {
window.electronAPI

View File

@ -6,10 +6,10 @@ import ExploreModelFilter from "../ExploreModelFilter";
const ExploreModelContainer: React.FC = () => (
<div className="flex flex-col flex-1 px-16 pt-14 overflow-hidden">
<HeaderTitle title="Explore Models" />
<SearchBar
{/* <SearchBar
type={SearchType.Model}
placeholder="Owner name like TheBloke, bhlim etc.."
/>
/> */}
<div className="flex flex-1 gap-x-10 mt-9 overflow-hidden">
<ExploreModelFilter />
<ExploreModelList />

View File

@ -1,7 +1,8 @@
import React from "react";
import SearchBar from "../SearchBar";
import SimpleCheckbox from "../SimpleCheckbox";
import SimpleTag, { TagType } from "../SimpleTag";
import SimpleTag from "../SimpleTag";
import { TagType } from "../SimpleTag/TagType";
const tags = [
"Roleplay",

View File

@ -4,10 +4,11 @@
import ExploreModelItemHeader from "../ExploreModelItemHeader";
import ModelVersionList from "../ModelVersionList";
import { Fragment, forwardRef, useState } from "react";
import SimpleTag, { TagType } from "../SimpleTag";
import { Fragment, forwardRef, useEffect, useState } from "react";
import SimpleTag from "../SimpleTag";
import { displayDate } from "@/_utils/datetime";
import { Product } from "@/_models/Product";
import useGetMostSuitableModelVersion from "@/_hooks/useGetMostSuitableModelVersion";
type Props = {
model: Product;
@ -16,15 +17,26 @@ type Props = {
const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
const [show, setShow] = useState(false);
const { availableVersions } = model;
const { suitableModel, getMostSuitableModelVersion } =
useGetMostSuitableModelVersion();
useEffect(() => {
getMostSuitableModelVersion(availableVersions);
}, [availableVersions]);
if (!suitableModel) {
return null;
}
return (
<div
ref={ref}
className="flex flex-col border border-gray-200 rounded-md mb-4"
>
<ExploreModelItemHeader
name={model.name}
status={TagType.Recommended}
versions={model.availableVersions}
suitableModel={suitableModel}
exploreModel={model}
/>
<div className="flex flex-col px-[26px] py-[22px]">
<div className="flex justify-between">
@ -42,11 +54,11 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
Hardware Compatibility
</div>
<div className="flex gap-2">
<SimpleTag
{/* <SimpleTag
clickable={false}
title={TagType.Compatible}
type={TagType.Compatible}
/>
/> */}
</div>
</div>
</div>
@ -63,11 +75,11 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
<div className="text-sm font-medium text-gray-500">
Expected Performance
</div>
<SimpleTag
{/* <SimpleTag
title={TagType.Medium}
type={TagType.Medium}
clickable={false}
/>
/> */}
</div>
</div>
</div>
@ -77,8 +89,14 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
{model.longDescription}
</span>
</div>
<div className="flex flex-col">
<div className="flex flex-col mt-5">
<span className="text-sm font-medium text-gray-500">Tags</span>
<div className="flex flex-wrap gap-2">
{model.tags.map((tag) => (
// @ts-ignore
<SimpleTag key={tag} title={tag} type={tag} clickable={false} />
))}
</div>
</div>
</div>
{model.availableVersions?.length > 0 && (
@ -87,6 +105,7 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
<ModelVersionList
model={model}
versions={model.availableVersions}
recommendedVersion={suitableModel?.id ?? ""}
/>
)}
<button

View File

@ -1,34 +1,74 @@
import SimpleTag, { TagType } from "../SimpleTag";
import SimpleTag from "../SimpleTag";
import PrimaryButton from "../PrimaryButton";
import { formatDownloadPercentage, toGigabytes } from "@/_utils/converter";
import { DownloadState } from "@/_models/DownloadState";
import SecondaryButton from "../SecondaryButton";
import { Product } from "@/_models/Product";
import { useCallback, useEffect, useMemo } from "react";
import { ModelVersion } from "@/_models/ModelVersion";
import useGetPerformanceTag from "@/_hooks/useGetPerformanceTag";
import useDownloadModel from "@/_hooks/useDownloadModel";
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
import { atom, useAtomValue, useSetAtom } from "jotai";
import {
MainViewState,
setMainViewStateAtom,
} from "@/_helpers/atoms/MainView.atom";
type Props = {
name: string;
status: TagType;
versions: ModelVersion[];
size?: number;
downloadState?: DownloadState;
onDownloadClick?: () => void;
suitableModel: ModelVersion;
exploreModel: Product;
};
const ExploreModelItemHeader: React.FC<Props> = ({
name,
status,
size,
versions,
downloadState,
onDownloadClick,
suitableModel,
exploreModel,
}) => {
const { downloadModel } = useDownloadModel();
const { downloadedModels } = useGetDownloadedModels();
const { performanceTag, title, getPerformanceForModel } =
useGetPerformanceTag();
const downloadAtom = useMemo(
() => atom((get) => get(modelDownloadStateAtom)[suitableModel.id]),
[suitableModel.id]
);
const downloadState = useAtomValue(downloadAtom);
const setMainViewState = useSetAtom(setMainViewStateAtom);
useEffect(() => {
getPerformanceForModel(suitableModel);
}, [suitableModel]);
const onDownloadClick = useCallback(() => {
downloadModel(exploreModel, suitableModel);
}, [exploreModel, suitableModel]);
const isDownloaded =
downloadedModels.find((model) => model.id === suitableModel.id) != null;
let downloadButton = (
<PrimaryButton
title={size ? `Download (${toGigabytes(size)})` : "Download"}
onClick={() => onDownloadClick?.()}
title={
suitableModel.size
? `Download (${toGigabytes(suitableModel.size)})`
: "Download"
}
onClick={() => onDownloadClick()}
/>
);
if (isDownloaded) {
downloadButton = (
<PrimaryButton
title="View Downloaded Model"
onClick={() => {
setMainViewState(MainViewState.MyModel);
}}
className="bg-green-500 hover:bg-green-400"
/>
);
}
if (downloadState != null) {
// downloading
downloadButton = (
@ -39,15 +79,15 @@ const ExploreModelItemHeader: React.FC<Props> = ({
)})`}
/>
);
} else if (versions.length === 0) {
downloadButton = <SecondaryButton disabled title="No files available" />;
}
return (
<div className="flex items-center justify-between p-4 border-b border-gray-200">
<div className="flex items-center gap-2">
<span>{name}</span>
<SimpleTag title={status} type={status} clickable={false} />
<span>{exploreModel.name}</span>
{performanceTag && (
<SimpleTag title={title} type={performanceTag} clickable={false} />
)}
</div>
{downloadButton}
</div>

View File

@ -6,14 +6,26 @@ import useDownloadModel from "@/_hooks/useDownloadModel";
import { modelDownloadStateAtom } from "@/_helpers/atoms/DownloadState.atom";
import { atom, useAtomValue } from "jotai";
import { ModelVersion } from "@/_models/ModelVersion";
import { useGetDownloadedModels } from "@/_hooks/useGetDownloadedModels";
import SimpleTag from "../SimpleTag";
import { ModelPerformance } from "../SimpleTag/TagType";
type Props = {
model: Product;
modelVersion: ModelVersion;
isRecommended: boolean;
};
const ModelVersionItem: React.FC<Props> = ({ model, modelVersion }) => {
const ModelVersionItem: React.FC<Props> = ({
model,
modelVersion,
isRecommended,
}) => {
const { downloadModel } = useDownloadModel();
const { downloadedModels } = useGetDownloadedModels();
const isDownloaded =
downloadedModels.find((model) => model.id === modelVersion.id) != null;
const downloadAtom = useMemo(
() => atom((get) => get(modelDownloadStateAtom)[modelVersion.id ?? ""]),
[modelVersion.id ?? ""]
@ -37,6 +49,8 @@ const ModelVersionItem: React.FC<Props> = ({ model, modelVersion }) => {
downloadButton = (
<div>{formatDownloadPercentage(downloadState.percent)}</div>
);
} else if (isDownloaded) {
downloadButton = <div>Downloaded</div>;
}
return (
@ -46,6 +60,13 @@ const ModelVersionItem: React.FC<Props> = ({ model, modelVersion }) => {
<span className="font-sm text-gray-900">{modelVersion.name}</span>
</div>
<div className="flex items-center gap-4">
{isRecommended && (
<SimpleTag
title={"Recommended"}
type={ModelPerformance.PerformancePositive}
clickable={false}
/>
)}
<div className="px-2.5 py-0.5 bg-gray-200 text-xs font-medium rounded">
{toGigabytes(modelVersion.size)}
</div>

View File

@ -6,17 +6,31 @@ import { ModelVersion } from "@/_models/ModelVersion";
type Props = {
model: Product;
versions: ModelVersion[];
recommendedVersion: string;
};
const ModelVersionList: React.FC<Props> = ({ model, versions }) => (
const ModelVersionList: React.FC<Props> = ({
model,
versions,
recommendedVersion,
}) => {
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="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.id} model={model} modelVersion={item} />
<ModelVersionItem
key={item.id}
model={model}
modelVersion={item}
isRecommended={item.id === recommendedVersion}
/>
))}
</div>
</div>
);
};
export default ModelVersionList;

View File

@ -0,0 +1,16 @@
import { TagType } from "./TagType";
export const tagStyleMapper: Record<TagType, string> = {
GGUF: "bg-yellow-100 text-yellow-800",
PerformancePositive:
"text-green-700 ring-1 ring-inset ring-green-600/20 bg-green-50",
PerformanceNeutral:
"bg-yellow-50 text-yellow-800 ring-1 ring-inset ring-yellow-600/20",
PerformanceNegative:
"bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
HardwareCompatible: "bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
HardwareIncompatible:
"bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
FreeStyle: "bg-gray-100 text-gray-800",
ExpectPerformanceMedium: "bg-yellow-100 text-yellow-800",
};

View File

@ -0,0 +1,32 @@
export enum ModelPerformance {
PerformancePositive = "PerformancePositive",
PerformanceNeutral = "PerformanceNeutral",
PerformanceNegative = "PerformanceNegative",
}
export enum HardwareCompatibility {
HardwareCompatible = "HardwareCompatible",
HardwareIncompatible = "HardwareIncompatible",
}
export enum ExpectedPerformance {
ExpectPerformanceMedium = "ExpectPerformanceMedium",
}
export enum ModelFormat {
GGUF = "GGUF",
}
export enum FreestyleTag {
FreeStyle = "FreeStyle",
}
export type TagType =
| ModelPerformance
| HardwareCompatibility
| ExpectedPerformance
| ModelFormat
| FreestyleTag;

View File

@ -1,57 +1,6 @@
import React from "react";
export enum TagType {
Roleplay = "Roleplay",
Llama = "Llama",
Story = "Story",
Casual = "Casual",
Professional = "Professional",
CodeLlama = "CodeLlama",
Coding = "Coding",
// Positive
Recommended = "Recommended",
Compatible = "Compatible",
// Neutral
SlowOnDevice = "This model will be slow on your device",
// Negative
InsufficientRam = "Insufficient RAM",
Incompatible = "Incompatible with your device",
TooLarge = "This model is too large for your device",
// Performance
Medium = "Medium",
BalancedQuality = "Balanced Quality",
}
const tagStyleMapper: Record<TagType, string> = {
[TagType.Roleplay]: "bg-red-100 text-red-800",
[TagType.Llama]: "bg-green-100 text-green-800",
[TagType.Story]: "bg-blue-100 text-blue-800",
[TagType.Casual]: "bg-yellow-100 text-yellow-800",
[TagType.Professional]: "text-indigo-800 bg-indigo-100",
[TagType.CodeLlama]: "bg-pink-100 text-pink-800",
[TagType.Coding]: "text-purple-800 bg-purple-100",
[TagType.Recommended]:
"text-green-700 ring-1 ring-inset ring-green-600/20 bg-green-50",
[TagType.Compatible]:
"bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
[TagType.SlowOnDevice]:
"bg-yellow-50 text-yellow-800 ring-1 ring-inset ring-yellow-600/20",
[TagType.Incompatible]:
"bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
[TagType.InsufficientRam]:
"bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
[TagType.TooLarge]: "bg-red-50 ext-red-700 ring-1 ring-inset ring-red-600/10",
[TagType.Medium]: "bg-yellow-100 text-yellow-800",
[TagType.BalancedQuality]: "bg-yellow-100 text-yellow-800",
};
import { TagType } from "./TagType";
import { tagStyleMapper } from "./TagStyleMapper";
type Props = {
title: string;

View File

@ -1,5 +1,4 @@
import { useState } from "react";
import { searchModels } from "./useGetDownloadedModels";
import { SearchModelParamHf } from "@/_models/hf/SearchModelParam.hf";
import { Product } from "@/_models/Product";
import { useSetAtom } from "jotai";
@ -22,14 +21,14 @@ export default function useGetHuggingFaceModel() {
search: { owner },
limit: 5,
};
const result = await searchModels(searchParams);
console.debug("result", JSON.stringify(result));
if (owner !== currentOwner) {
setModelList(result.data);
setCurrentOwner(owner);
} else {
setModelList([...modelList, ...result.data]);
}
// const result = await searchModels(searchParams);
// console.debug("result", JSON.stringify(result));
// if (owner !== currentOwner) {
// setModelList(result.data);
// setCurrentOwner(owner);
// } else {
// setModelList([...modelList, ...result.data]);
// }
setLoadMoreInProgress(false);
};

View File

@ -0,0 +1,29 @@
import { executeSerial } from "@/_services/pluginService";
import { SystemMonitoringService } from "../../shared/coreService";
import { ModelVersion } from "@/_models/ModelVersion";
import { useState } from "react";
export default function useGetMostSuitableModelVersion() {
const [suitableModel, setSuitableModel] = useState<ModelVersion | undefined>();
const getMostSuitableModelVersion = async (modelVersions: ModelVersion[]) => {
const resourceInfo = await executeSerial(
SystemMonitoringService.GET_RESOURCES_INFORMATION
);
const totalRam = resourceInfo.mem.total;
// find the model version with the highest required RAM that is still below the user's RAM by 80%
const modelVersion = modelVersions.reduce((prev, current) => {
if (current.maxRamRequired > prev.maxRamRequired) {
if (current.maxRamRequired < totalRam * 0.8) {
return current;
}
}
return prev;
});
setSuitableModel(modelVersion);
};
return { suitableModel, getMostSuitableModelVersion };
}

View File

@ -0,0 +1,53 @@
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
import { SystemMonitoringService } from "../../shared/coreService";
import { useState } from "react";
import { ModelVersion } from "@/_models/ModelVersion";
import { ModelPerformance, TagType } from "@/_components/SimpleTag/TagType";
// Recommendation:
// `Recommended (green)`: "Max RAM required" is 80% of users max RAM.
// `Slow on your device (yellow)`: Max RAM required is 80-100% of users max RAM
// `Not enough RAM (red)`: User RAM is below "Max RAM required"
export default function useGetPerformanceTag() {
const [performanceTag, setPerformanceTag] = useState<TagType | undefined>();
const getPerformanceForModel = async (modelVersion: ModelVersion) => {
const resourceInfo = await executeSerial(
SystemMonitoringService.GET_RESOURCES_INFORMATION
);
const totalRam = resourceInfo.mem.total;
const requiredRam = modelVersion.maxRamRequired;
setPerformanceTag(calculateRamPerformance(requiredRam, totalRam));
};
let title = "";
switch (performanceTag) {
case ModelPerformance.PerformancePositive:
title = "Recommended";
break;
case ModelPerformance.PerformanceNeutral:
title = "Slow on your device";
break;
case ModelPerformance.PerformanceNegative:
title = "Not enough RAM";
break;
}
return { performanceTag, title, getPerformanceForModel };
}
const calculateRamPerformance = (
requiredRamAmt: number,
totalRamAmt: number
) => {
const percentage = requiredRamAmt / totalRamAmt;
if (percentage < 0.8) {
return ModelPerformance.PerformancePositive;
} else if (percentage >= 0.8 && percentage < 1) {
return ModelPerformance.PerformanceNeutral;
} else {
return ModelPerformance.PerformanceNegative;
}
};

View File

@ -57,9 +57,9 @@ export type AssistantModel = {
updatedAt?: number;
status: string; // TODO: add this in the database // Downloaded, Active
status: string;
releaseDate: number; // TODO: add this in the database
releaseDate: number;
tags: string[];
};

View File

@ -24,8 +24,8 @@ export interface Product {
outputs?: ProductOutput;
createdAt: number;
updatedAt?: number;
status: string; // TODO: add this in the database // Downloaded, Active
releaseDate: number; // TODO: add this in the database
status: string;
releaseDate: number;
tags: string[];
availableVersions: ModelVersion[];
}