Skip to content

Commit

Permalink
feat: Model Info popup (#36)
Browse files Browse the repository at this point in the history
* Add Model Info Menu

* prettier formatting, slight colour modifications

* refine the layout and add more info from knownmodel list

---------

Co-authored-by: L❤️ ☮️ ✋ <[email protected]>
  • Loading branch information
Regular-Baf and louisgv authored Jun 17, 2023
1 parent 6ee235d commit 385ba8b
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 115 deletions.
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ tracing = "0.1.36"
tracing-subscriber = "0.3.15"
url = "2.3.1"
kv = { version = "0.24.0", features = ["json-value"] }
tauri = { version = "1.3.0", features = ["dialog-message", "dialog-open", "http-all", "os-all", "updater", "window-center", "window-maximize", "window-set-title", "window-show"] }
tauri = { version = "1.3.0", features = ["dialog-confirm", "dialog-message", "dialog-open", "http-all", "os-all", "updater", "window-center", "window-maximize", "window-set-title", "window-show"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
anyhow = "1.0.71"
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"dialog": {
"all": false,
"open": true,
"message": true
"message": true,
"confirm": true
},
"window": {
"all": false,
Expand Down
10 changes: 8 additions & 2 deletions apps/desktop/src/features/inference-server/model-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "@localai/ui/select"
import { modelTypeList } from "@models/index"
import { TrashIcon } from "@radix-ui/react-icons"
import { confirm } from "@tauri-apps/api/dialog"
import { useState } from "react"

import { InvokeCommand, invoke } from "~features/invoke"
Expand Down Expand Up @@ -69,14 +70,19 @@ export const ModelConfig = () => {
value={label}
onChange={(e) => setLabel(e.target.value)}
/> */}
<div className="flex gap-2">
<div className="flex gap-3">
<SpinnerButton
className={cn(
"w-10 p-1 justify-center",
"group-hover:opacity-100 opacity-0 transition-opacity",
downloadState === DownloadState.Downloading ? "hidden" : "block"
downloadState === DownloadState.Downloading ? "hidden" : "flex"
)}
Icon={TrashIcon}
onClick={async () => {
if (!(await confirm(`Deleting ${model.name}?`))) {
return
}

await invoke(InvokeCommand.DeleteModelFile, {
path: model.path
})
Expand Down
84 changes: 16 additions & 68 deletions apps/desktop/src/features/inference-server/model-digest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ import { cn } from "@localai/theme/utils"
import { SpinnerButton } from "@localai/ui/button"
import { CrossCircledIcon, ReloadIcon } from "@radix-ui/react-icons"
import { ShoppingCodeCheck } from "iconoir-react"
import { useEffect, useState } from "react"

import { InitState, useInit } from "~features/inference-server/use-init"
import { InvokeCommand, invoke } from "~features/invoke"
import type { ModelIntegrity } from "~features/invoke/model-integrity"
import type { ModelMetadata } from "~features/model-downloader/model-file"
import { InitState } from "~features/inference-server/use-init"
import { useOverlayPopup } from "~features/inference-server/use-overlay-popup"
import { DownloadState } from "~features/model-downloader/use-model-download"
import { useGlobal } from "~providers/global"
import { useModel } from "~providers/model"

export const getTruncatedHash = (hashValue: string) =>
Expand All @@ -26,71 +22,24 @@ const HashDisplay = ({ hashType = "", hashValue = "", truncated = false }) => {
)
}

export const getCachedIntegrity = async (path: string) =>
invoke(InvokeCommand.GetCachedIntegrity, {
path
}).catch<ModelIntegrity>(() => null)

export function ModelDigest({ model }: { model: ModelMetadata }) {
export function ModelDigest() {
const overlayPopup = useOverlayPopup()
const {
knownModels: { modelMap }
} = useGlobal()

const { downloadState, updateModelConfig, updateModelType } = useModel()
const [integrity, setIntegrity] = useState<ModelIntegrity>(null)
const [isCalculating, setIsCalculating] = useState(false)
const [showDetail, setShowDetail] = useState(false)
const { initState } = useInit(async () => {
const resp = await getCachedIntegrity(model.path)
setIntegrity(resp)
}, [model])

useEffect(() => {
if (downloadState === DownloadState.Completed) {
getCachedIntegrity(model.path).then(setIntegrity)
}
}, [model, downloadState])

async function computeDigest() {
setIntegrity(null)
setIsCalculating(true)
try {
const resp = await invoke(InvokeCommand.ComputeModelIntegrity, {
path: model.path
})
setIntegrity(resp)

const knownModelMetadata = modelMap[resp.blake3]
if (!!knownModelMetadata) {
alert(
"Known model metadata found, updating model config:\n\n" +
JSON.stringify(knownModelMetadata, null, 2)
)
updateModelType(knownModelMetadata.modelType)
updateModelConfig({
tokenizer: knownModelMetadata.tokenizers?.[0] || "", // Pick the first one for now
defaultPromptTemplate: knownModelMetadata.promptTemplate || ""
})
} else {
alert(
"No known model metadata found. The model might need manual configuration."
)
}
} catch (error) {
alert(error)
}

setIsCalculating(false)
}

integrity,
isChecking: isCalculating,
checkModel: computeDigest,
downloadState,
integrityInit: { initState }
} = useModel()
return (
<div className="flex justify-end text-gray-10 w-64 relative">
{integrity ? (
<>
<div
ref={overlayPopup.popupRef}
className={cn(
showDetail
? "z-10 opacity-100 pointer-events-auto"
overlayPopup.isVisible
? "z-40 opacity-100 pointer-events-auto"
: "z-0 opacity-0 pointer-events-none",
"absolute right-0 top-0",
"transition-opacity",
Expand All @@ -104,7 +53,7 @@ export function ModelDigest({ model }: { model: ModelMetadata }) {
)}>
<button
className="absolute right-1 top-1"
onClick={() => setShowDetail(false)}>
onClick={() => overlayPopup.setIsVisible(false)}>
<CrossCircledIcon />
</button>
{["blake3", "sha256"].map((hashType) => (
Expand All @@ -124,10 +73,9 @@ export function ModelDigest({ model }: { model: ModelMetadata }) {
onClick={computeDigest}
/>
<button
ref={overlayPopup.buttonRef}
className="text-xs hover:bg-gray-4 p-2 rounded-md"
onClick={() => {
setShowDetail(true)
}}>
onClick={() => overlayPopup.setIsVisible(true)}>
{["blake3", "sha256"].map((hashType) => (
<HashDisplay
key={hashType}
Expand Down
82 changes: 82 additions & 0 deletions apps/desktop/src/features/inference-server/model-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { cn } from "@localai/theme/utils"
import { Button } from "@localai/ui/button"
import { CrossCircledIcon, InfoCircledIcon } from "@radix-ui/react-icons"

import { useOverlayPopup } from "~features/inference-server/use-overlay-popup"
import { useModel } from "~providers/model"

export const ModelInfo = () => {
const overlayPopup = useOverlayPopup()
const { knownModelInfo, integrity, checkModel, isChecking } = useModel()

return (
<div className="relative text-gray-10">
<Button
ref={overlayPopup.buttonRef}
disabled={isChecking}
onClick={async () => {
if (!integrity) {
await checkModel()
}
overlayPopup.setIsVisible(true)
}}
className={cn(
"w-6 h-6 p-1 justify-center",
"group-hover:opacity-100 opacity-0 transition-opacity"
)}>
<InfoCircledIcon />
</Button>

<div
ref={overlayPopup.popupRef}
className={cn(
"absolute top-0 left-0",
"transition-opacity",
"text-xs sm:text-sm md:text-base",
knownModelInfo ? "w-64 sm:w-64 md:w-96" : "w-36",
overlayPopup.isVisible
? "z-40 opacity-100 pointer-events-auto"
: "z-0 opacity-0 pointer-events-none"
)}>
<div
className={cn(
"bg-gray-4 relative px-5 py-4 rounded-lg border border-gray-6",
"flex-wrap"
)}>
<button
className={cn("absolute left-1 top-1")}
onClick={() => overlayPopup.setIsVisible(false)}>
<CrossCircledIcon />
</button>
{!knownModelInfo ? (
<span className="text-xs font-bold">NOT AVAILABLE</span>
) : (
<div
className={cn(
"gap-2 w-full grid text-xs",
"grid-cols-[2fr_5fr]"
)}>
<label className={cn("font-bold")}>ORIGIN</label>
<code className="text-right break-all">
{knownModelInfo.downloadUrl.split("/").pop()}
</code>

<label className={cn("font-bold")}>TYPE</label>
<code className="text-right">{knownModelInfo.modelType}</code>

<label className={cn("font-bold")}>DESCRIPTION</label>
<code className="text-right whitespace-pre-line">
{knownModelInfo.description}
</code>

<label className={cn("font-bold")}>LICENSE</label>
<code className="text-right">
{knownModelInfo.licenses.join(", ")}
</code>
</div>
)}
</div>
</div>
</div>
)
}
22 changes: 15 additions & 7 deletions apps/desktop/src/features/inference-server/model-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { cn } from "@localai/theme/utils"

import { ModelConfig } from "~features/inference-server/model-config"
import { ModelDigest } from "~features/inference-server/model-digest"
import { ModelInfo } from "~features/inference-server/model-info"
import { useToggle } from "~features/layout/use-toggle"
import { type ModelMetadata, toGB } from "~features/model-downloader/model-file"
import { useGlobal } from "~providers/global"
Expand All @@ -10,12 +11,19 @@ import { ModelProvider, useModel } from "~providers/model"
const ModelLabel = () => {
const { model, modelSize } = useModel()
const [showByte, toggleShowByte] = useToggle()

return (
<div className="flex flex-col justify-between w-full">
<div className={"text-md"}>{model.name}</div>
<button className="flex text-xs text-gray-10" onClick={toggleShowByte}>
{showByte ? `${modelSize} B` : `${toGB(modelSize).toFixed(2)} GB`}
</button>
<div className="flex flex-col w-full justify-start">
<div className="flex text-md gap-2">
<span>{model.name}</span>
<ModelInfo />
</div>
<div className="flex text-xs items-center text-gray-10 gap-1">
<span>{showByte ? modelSize : toGB(modelSize).toFixed(2)}</span>
<button className="hover:text-gray-11" onClick={toggleShowByte}>
{showByte ? `B` : `GB`}
</button>
</div>
</div>
)
}
Expand All @@ -35,9 +43,9 @@ export const ModelListItem = ({ model }: { model: ModelMetadata }) => {
? "border border-green-7 hover:border-green-8"
: "border border-gray-7 hover:border-gray-8"
)}>
<div className="flex items-center justify-between w-full">
<div className="flex justify-between w-full">
<ModelLabel />
<ModelDigest model={model} />
<ModelDigest />
</div>
<ModelConfig />
</div>
Expand Down
36 changes: 36 additions & 0 deletions apps/desktop/src/features/inference-server/use-overlay-popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useRef, useState } from "react"

export const useOverlayPopup = () => {
const [isVisible, setIsVisible] = useState(false)
const popupRef = useRef(null)
const buttonRef = useRef(null)

const toggle = () => setIsVisible((v) => !v)

useEffect(() => {
if (!isVisible) return

const handleDocumentClick = (event: MouseEvent) => {
if (
!popupRef.current.contains(event.target) &&
!buttonRef.current.contains(event.target)
) {
setIsVisible(false)
}
}

document.addEventListener("click", handleDocumentClick)

return () => {
document.removeEventListener("click", handleDocumentClick)
}
}, [isVisible])

return {
isVisible,
setIsVisible,
toggle,
popupRef,
buttonRef
}
}
Loading

1 comment on commit 385ba8b

@vercel
Copy link

@vercel vercel bot commented on 385ba8b Jun 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.