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

View File

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

View File

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

View File

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