chore: hub UI tooltip filter, max model size and search result (#4753)

* chore: fix hub ui tooltip, max-filter, and search result

* chore: fix linter
This commit is contained in:
Faisal Amir 2025-02-27 15:31:11 +07:00 committed by GitHub
parent f586c19951
commit 250d30d73a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 66 additions and 35 deletions

View File

@ -26,7 +26,7 @@ export default function ContextLengthFilter() {
className="flex-shrink-0 text-[hsl(var(--text-secondary))]"
/>
}
content={''}
content="Controls how much text the model can consider at once. Longer context allows the model to handle more input but uses more memory and runs slower."
/>
</div>
<div className="flex items-center gap-x-4">

View File

@ -8,29 +8,20 @@ import { InfoIcon } from 'lucide-react'
export const hubModelSizeMinAtom = atom(0)
export const hubModelSizeMaxAtom = atom(100)
export default function ModelSizeFilter() {
export default function ModelSizeFilter({ max }: { max: number }) {
const [value, setValue] = useAtom(hubModelSizeMinAtom)
const [valueMax, setValueMax] = useAtom(hubModelSizeMaxAtom)
const [inputingMinValue, setInputingMinValue] = useState(false)
const [inputingMaxValue, setInputingMaxValue] = useState(false)
const normalizeTextValue = (value: number) => {
return value === 100 ? '100GB' : value === 0 ? 0 : `${value}GB`
return value === 0 ? 0 : `${value}GB`
}
return (
<div className="flex flex-col">
<div className="mb-3 flex items-center gap-x-2">
<p className="font-semibold">Model size</p>
<Tooltip
trigger={
<InfoIcon
size={16}
className="flex-shrink-0 text-[hsl(var(--text-secondary))]"
/>
}
content={''}
/>
</div>
<div className="flex items-center gap-x-4">
<div className="relative w-full">
@ -41,7 +32,7 @@ export default function ModelSizeFilter() {
setValueMax(Number(e[1]))
}}
min={0}
max={100}
max={max}
step={1}
/>
</div>
@ -55,7 +46,7 @@ export default function ModelSizeFilter() {
type="text"
className="mt-1 h-8 w-[60px] p-2"
min={0}
max={100}
max={max}
value={inputingMinValue ? value : normalizeTextValue(value)}
textAlign="left"
onFocus={(e) => setInputingMinValue(true)}
@ -75,7 +66,7 @@ export default function ModelSizeFilter() {
// E.g. anything changes that trigger onValueChanged
// Which is incorrect
if (
Number(e.target.value) > 100 ||
Number(e.target.value) > max ||
Number(e.target.value) < 0 ||
Number.isNaN(Number(e.target.value))
)
@ -91,7 +82,7 @@ export default function ModelSizeFilter() {
type="text"
className="mt-1 h-8 w-[60px] p-2"
min={0}
max={100}
max={max}
value={inputingMaxValue ? valueMax : normalizeTextValue(valueMax)}
textAlign="left"
onFocus={(e) => setInputingMaxValue(true)}
@ -99,7 +90,7 @@ export default function ModelSizeFilter() {
setInputingMaxValue(false)
const numericValue = e.target.value.replace(/\D/g, '')
const value = Number(numericValue)
setValueMax(value > 100 ? 100 : value)
setValueMax(value > max ? max : value)
}}
onChange={(e) => {
// Passthru since it validates again onBlur
@ -111,7 +102,7 @@ export default function ModelSizeFilter() {
// E.g. anything changes that trigger onValueChanged
// Which is incorrect
if (
Number(e.target.value) > 100 ||
Number(e.target.value) > max ||
Number(e.target.value) < 0 ||
Number.isNaN(Number(e.target.value))
)

View File

@ -35,6 +35,7 @@ import {
import ModelList from '@/screens/Hub/ModelList'
import { toGigabytes } from '@/utils/converter'
import { extractModelRepo } from '@/utils/modelSource'
import { fuzzySearch } from '@/utils/search'
@ -110,6 +111,14 @@ const HubScreen = () => {
const [maxModelSizeFilter, setMaxModelSizeFilter] =
useAtom(hubModelSizeMaxAtom)
const largestModel =
sources &&
sources
.flatMap((model) => model.models)
.reduce((max, model) => (model.size > max.size ? model : max), {
size: 0,
})
const searchedModels = useMemo(
() =>
searchValue.length
@ -135,7 +144,7 @@ const HubScreen = () => {
!minModelSizeFilter ||
model.models.some((e) => e.size >= minModelSizeFilter * (1 << 30))
const matchesMaxSize =
maxModelSizeFilter === 100 ||
maxModelSizeFilter === largestModel?.size ||
model.models.some((e) => e.size <= maxModelSizeFilter * (1 << 30))
return isCompatible && matchesCtxLen && matchesMinSize && matchesMaxSize
@ -169,6 +178,19 @@ const HubScreen = () => {
}
}, [modelDetail, sources, setModelDetail, addModelSource])
useEffect(() => {
if (largestModel) {
setMaxModelSizeFilter(
Number(
toGigabytes(Number(largestModel?.size), {
hideUnit: true,
toFixed: 0,
})
)
)
}
}, [largestModel])
useEffect(() => {
if (selectedModel) {
// Try add the model source again to update it's data
@ -403,7 +425,7 @@ const HubScreen = () => {
<ModelSearch onSearchLocal={onSearchUpdate} />
<div
className={twMerge(
'invisible absolute mt-2 w-full overflow-hidden rounded-lg border border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))] shadow-lg',
'invisible absolute mt-2 max-h-[400px] w-full overflow-hidden rounded-lg border border-[hsla(var(--app-border))] bg-[hsla(var(--app-bg))] shadow-lg',
searchValue.length > 0 && 'visible'
)}
>
@ -414,7 +436,10 @@ const HubScreen = () => {
</span>
</div>
) : (
<>
<ScrollArea
type={showScrollBar ? 'always' : 'scroll'}
className="w-full"
>
{searchedModels.map((model) => (
<div
key={model.id}
@ -440,7 +465,7 @@ const HubScreen = () => {
</span>
</div>
))}
</>
</ScrollArea>
)}
</div>
</div>
@ -468,7 +493,7 @@ const HubScreen = () => {
onClick={() => {
setCtxLenFilter(0)
setMinModelSizeFilter(0)
setMaxModelSizeFilter(100)
setMaxModelSizeFilter(Number(largestModel?.size))
setCompatible(false)
}}
>
@ -487,7 +512,14 @@ const HubScreen = () => {
<ContextLengthFilter />
</div>
<div className="mt-12">
<ModelSizeFilter />
<ModelSizeFilter
max={Number(
toGigabytes(Number(largestModel?.size), {
hideUnit: true,
toFixed: 0,
})
)}
/>
</div>
</div>

View File

@ -70,8 +70,9 @@ const MessageContainer: React.FC<
return (
<div
className={twMerge(
'group relative mx-auto p-4',
chatWidth === 'compact' && 'max-w-[700px]'
'group relative mx-auto px-4 py-2',
chatWidth === 'compact' && 'max-w-[700px]',
isUser && 'pb-4 pt-0'
)}
>
<div
@ -109,11 +110,9 @@ const MessageContainer: React.FC<
<div
className={twMerge(
'absolute right-0 order-1 mt-2 flex cursor-pointer items-center justify-start gap-x-2 transition-all',
isUser &&
'hidden group-hover:absolute group-hover:right-4 group-hover:top-4 group-hover:flex',
props.isCurrentMessage && !isUser
? 'relative left-0 order-2 flex w-full justify-between'
: 'relative left-0 order-2 hidden w-full justify-between group-hover:flex'
isUser
? 'hidden group-hover:absolute group-hover:right-4 group-hover:top-4 group-hover:flex'
: 'relative left-0 order-2 -mt-1 flex w-full justify-between opacity-0 group-hover:opacity-100'
)}
>
<div>

View File

@ -1,14 +1,23 @@
export const toGigabytes = (
input: number,
options?: { hideUnit?: boolean }
options?: { hideUnit?: boolean; toFixed?: number }
) => {
if (!input) return ''
if (input > 1024 ** 3) {
return (input / 1024 ** 3).toFixed(2) + (options?.hideUnit ? '' : 'GB')
return (
(input / 1024 ** 3).toFixed(options?.toFixed ?? 2) +
(options?.hideUnit ? '' : 'GB')
)
} else if (input > 1024 ** 2) {
return (input / 1024 ** 2).toFixed(2) + (options?.hideUnit ? '' : 'MB')
return (
(input / 1024 ** 2).toFixed(options?.toFixed ?? 2) +
(options?.hideUnit ? '' : 'MB')
)
} else if (input > 1024) {
return (input / 1024).toFixed(2) + (options?.hideUnit ? '' : 'KB')
return (
(input / 1024).toFixed(options?.toFixed ?? 2) +
(options?.hideUnit ? '' : 'KB')
)
} else {
return input + (options?.hideUnit ? '' : 'B')
}