feat: add blank state component (#3413)
This commit is contained in:
parent
d14ce6fb86
commit
e9c30450b9
24
web/containers/BlankState/index.tsx
Normal file
24
web/containers/BlankState/index.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
action?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const BlankState = ({ title, description, action }: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="mx-auto mt-10 flex h-full w-3/4 flex-col items-center justify-center text-center">
|
||||||
|
<LogoMark className="mx-auto mb-4 animate-wave" width={32} height={32} />
|
||||||
|
<h1 className="text-base font-semibold">{title}</h1>
|
||||||
|
{description && (
|
||||||
|
<p className="mt-1 text-[hsla(var(--text-secondary))]">{description}</p>
|
||||||
|
)}
|
||||||
|
{action && action}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BlankState
|
||||||
@ -7,6 +7,7 @@ import { ScrollArea, Button, Select } from '@janhq/joi'
|
|||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
import { UploadIcon } from 'lucide-react'
|
import { UploadIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import BlankState from '@/containers/BlankState'
|
||||||
import CenterPanelContainer from '@/containers/CenterPanelContainer'
|
import CenterPanelContainer from '@/containers/CenterPanelContainer'
|
||||||
import ModelSearch from '@/containers/ModelSearch'
|
import ModelSearch from '@/containers/ModelSearch'
|
||||||
|
|
||||||
@ -92,15 +93,19 @@ const HubScreen = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 py-0 sm:px-16">
|
<div className="p-4 py-0 sm:px-16">
|
||||||
<div className="mb-4 flex w-full justify-end">
|
{!filteredModels.length ? (
|
||||||
<Select
|
<BlankState title="No search results found" />
|
||||||
value={sortSelected}
|
) : (
|
||||||
onValueChange={(value) => {
|
<div className="mb-4 flex w-full justify-end">
|
||||||
setSortSelected(value)
|
<Select
|
||||||
}}
|
value={sortSelected}
|
||||||
options={sortMenus}
|
onValueChange={(value) => {
|
||||||
/>
|
setSortSelected(value)
|
||||||
</div>
|
}}
|
||||||
|
options={sortMenus}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<ModelList models={filteredModels} />
|
<ModelList models={filteredModels} />
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import BlankState from '@/containers/BlankState'
|
||||||
import ModelSearch from '@/containers/ModelSearch'
|
import ModelSearch from '@/containers/ModelSearch'
|
||||||
|
|
||||||
import SetupRemoteModel from '@/containers/SetupRemoteModel'
|
import SetupRemoteModel from '@/containers/SetupRemoteModel'
|
||||||
@ -183,72 +184,80 @@ const MyModels = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
{groupByEngine.map((engine, i) => {
|
{!groupByEngine.length ? (
|
||||||
const engineLogo = getLogoEngine(engine as InferenceEngine)
|
<div className="mt-8">
|
||||||
const showModel = showEngineListModel.includes(engine)
|
<BlankState title="No search results found" />
|
||||||
const onClickChevron = () => {
|
</div>
|
||||||
if (showModel) {
|
) : (
|
||||||
setShowEngineListModel((prev) =>
|
groupByEngine.map((engine, i) => {
|
||||||
prev.filter((item) => item !== engine)
|
const engineLogo = getLogoEngine(engine as InferenceEngine)
|
||||||
)
|
const showModel = showEngineListModel.includes(engine)
|
||||||
} else {
|
const onClickChevron = () => {
|
||||||
setShowEngineListModel((prev) => [...prev, engine])
|
if (showModel) {
|
||||||
|
setShowEngineListModel((prev) =>
|
||||||
|
prev.filter((item) => item !== engine)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setShowEngineListModel((prev) => [...prev, engine])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return (
|
||||||
return (
|
<div className="my-6" key={i}>
|
||||||
<div className="my-6" key={i}>
|
<div className="flex flex-col items-start justify-start gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div className="flex flex-col items-start justify-start gap-2 sm:flex-row sm:items-center sm:justify-between">
|
<div
|
||||||
<div
|
className="mb-1 mt-3 flex cursor-pointer items-center gap-2"
|
||||||
className="mb-1 mt-3 flex cursor-pointer items-center gap-2"
|
onClick={onClickChevron}
|
||||||
onClick={onClickChevron}
|
>
|
||||||
>
|
{engineLogo && (
|
||||||
{engineLogo && (
|
<Image
|
||||||
<Image
|
className="h-6 w-6 flex-shrink-0"
|
||||||
className="h-6 w-6 flex-shrink-0"
|
width={48}
|
||||||
width={48}
|
height={48}
|
||||||
height={48}
|
src={engineLogo}
|
||||||
src={engineLogo}
|
alt="logo"
|
||||||
alt="logo"
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
<h6 className="font-medium text-[hsla(var(--text-secondary))]">
|
||||||
<h6 className="font-medium text-[hsla(var(--text-secondary))]">
|
{getTitleByEngine(engine)}
|
||||||
{getTitleByEngine(engine)}
|
</h6>
|
||||||
</h6>
|
</div>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{!localEngines.includes(engine) && (
|
||||||
|
<SetupRemoteModel engine={engine} />
|
||||||
|
)}
|
||||||
|
{!showModel ? (
|
||||||
|
<Button theme="icon" onClick={onClickChevron}>
|
||||||
|
<ChevronDownIcon
|
||||||
|
size={14}
|
||||||
|
className="text-[hsla(var(--text-secondary))]"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button theme="icon" onClick={onClickChevron}>
|
||||||
|
<ChevronUpIcon
|
||||||
|
size={14}
|
||||||
|
className="text-[hsla(var(--text-secondary))]"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="mt-2">
|
||||||
{!localEngines.includes(engine) && (
|
{filteredDownloadedModels
|
||||||
<SetupRemoteModel engine={engine} />
|
? filteredDownloadedModels
|
||||||
)}
|
.filter((x) => x.engine === engine)
|
||||||
{!showModel ? (
|
.map((model) => {
|
||||||
<Button theme="icon" onClick={onClickChevron}>
|
if (!showModel) return null
|
||||||
<ChevronDownIcon
|
return (
|
||||||
size={14}
|
<MyModelList key={model.id} model={model} />
|
||||||
className="text-[hsla(var(--text-secondary))]"
|
)
|
||||||
/>
|
})
|
||||||
</Button>
|
: null}
|
||||||
) : (
|
|
||||||
<Button theme="icon" onClick={onClickChevron}>
|
|
||||||
<ChevronUpIcon
|
|
||||||
size={14}
|
|
||||||
className="text-[hsla(var(--text-secondary))]"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
)
|
||||||
{filteredDownloadedModels
|
})
|
||||||
? filteredDownloadedModels
|
)}
|
||||||
.filter((x) => x.engine === engine)
|
|
||||||
.map((model) => {
|
|
||||||
if (!showModel) return null
|
|
||||||
return <MyModelList key={model.id} model={model} />
|
|
||||||
})
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user