perf: improve modellist search bar responsiveness (and memorization) (#4221)
This commit is contained in:
parent
3dc4947e26
commit
b43ecb75f5
@ -16,11 +16,11 @@ import { useProvider } from '@renderer/hooks/useProvider'
|
|||||||
import { ModelCheckStatus } from '@renderer/services/HealthCheckService'
|
import { ModelCheckStatus } from '@renderer/services/HealthCheckService'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setModel } from '@renderer/store/assistants'
|
import { setModel } from '@renderer/store/assistants'
|
||||||
import { Model, Provider } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import { maskApiKey } from '@renderer/utils/api'
|
import { maskApiKey } from '@renderer/utils/api'
|
||||||
import { Avatar, Button, Card, Flex, Space, Tooltip, Typography } from 'antd'
|
import { Avatar, Button, Card, Flex, Space, Tooltip, Typography } from 'antd'
|
||||||
import { groupBy, sortBy, toPairs } from 'lodash'
|
import { groupBy, sortBy, toPairs } from 'lodash'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { memo, useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ const STATUS_COLORS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ModelListProps {
|
interface ModelListProps {
|
||||||
provider: Provider
|
providerId: string
|
||||||
modelStatuses?: ModelStatus[]
|
modelStatuses?: ModelStatus[]
|
||||||
searchText?: string
|
searchText?: string
|
||||||
}
|
}
|
||||||
@ -166,10 +166,9 @@ function useModelStatusRendering() {
|
|||||||
return { renderStatusIndicator, renderLatencyText }
|
return { renderStatusIndicator, renderLatencyText }
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModelList: React.FC<ModelListProps> = ({ provider: _provider, modelStatuses = [], searchText = '' }) => {
|
const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], searchText = '' }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { provider } = useProvider(_provider.id)
|
const { provider, updateProvider, models, removeModel } = useProvider(providerId)
|
||||||
const { updateProvider, models, removeModel } = useProvider(_provider.id)
|
|
||||||
const { assistants } = useAssistants()
|
const { assistants } = useAssistants()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const { defaultModel, setDefaultModel } = useDefaultModel()
|
const { defaultModel, setDefaultModel } = useDefaultModel()
|
||||||
@ -180,59 +179,64 @@ const ModelList: React.FC<ModelListProps> = ({ provider: _provider, modelStatuse
|
|||||||
const modelsWebsite = providerConfig?.websites?.models
|
const modelsWebsite = providerConfig?.websites?.models
|
||||||
|
|
||||||
const [editingModel, setEditingModel] = useState<Model | null>(null)
|
const [editingModel, setEditingModel] = useState<Model | null>(null)
|
||||||
const [debouncedSearchText, setDebouncedSearchText] = useState(searchText)
|
|
||||||
|
|
||||||
useEffect(() => {
|
const modelGroups = useMemo(() => {
|
||||||
const timer = setTimeout(() => {
|
const filteredModels = searchText
|
||||||
setDebouncedSearchText(searchText)
|
? models.filter((model) => model.name.toLowerCase().includes(searchText.toLowerCase()))
|
||||||
}, 50)
|
: models
|
||||||
|
return groupBy(filteredModels, 'group')
|
||||||
|
}, [searchText, models])
|
||||||
|
|
||||||
return () => clearTimeout(timer)
|
const sortedModelGroups = useMemo(() => {
|
||||||
}, [searchText])
|
return sortBy(toPairs(modelGroups), [0]).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = value
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}, [modelGroups])
|
||||||
|
|
||||||
const filteredModels = debouncedSearchText
|
const onManageModel = useCallback(() => {
|
||||||
? models.filter((model) => model.name.toLowerCase().includes(debouncedSearchText.toLowerCase()))
|
EditModelsPopup.show({ provider })
|
||||||
: models
|
}, [provider])
|
||||||
|
|
||||||
const modelGroups = groupBy(filteredModels, 'group')
|
const onAddModel = useCallback(
|
||||||
const sortedModelGroups = sortBy(toPairs(modelGroups), [0]).reduce((acc, [key, value]) => {
|
() => AddModelPopup.show({ title: t('settings.models.add.add_model'), provider }),
|
||||||
acc[key] = value
|
[provider, t]
|
||||||
return acc
|
)
|
||||||
}, {})
|
|
||||||
|
|
||||||
const onManageModel = () => EditModelsPopup.show({ provider })
|
const onEditModel = useCallback((model: Model) => {
|
||||||
const onAddModel = () => AddModelPopup.show({ title: t('settings.models.add.add_model'), provider })
|
|
||||||
const onEditModel = (model: Model) => {
|
|
||||||
setEditingModel(model)
|
setEditingModel(model)
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
const onUpdateModel = (updatedModel: Model) => {
|
const onUpdateModel = useCallback(
|
||||||
const updatedModels = models.map((m) => {
|
(updatedModel: Model) => {
|
||||||
if (m.id === updatedModel.id) {
|
const updatedModels = models.map((m) => {
|
||||||
return updatedModel
|
if (m.id === updatedModel.id) {
|
||||||
|
return updatedModel
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
})
|
||||||
|
|
||||||
|
updateProvider({ ...provider, models: updatedModels })
|
||||||
|
|
||||||
|
// Update assistants using this model
|
||||||
|
assistants.forEach((assistant) => {
|
||||||
|
if (assistant?.model?.id === updatedModel.id && assistant.model.provider === provider.id) {
|
||||||
|
dispatch(
|
||||||
|
setModel({
|
||||||
|
assistantId: assistant.id,
|
||||||
|
model: updatedModel
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update default model if needed
|
||||||
|
if (defaultModel?.id === updatedModel.id && defaultModel?.provider === provider.id) {
|
||||||
|
setDefaultModel(updatedModel)
|
||||||
}
|
}
|
||||||
return m
|
},
|
||||||
})
|
[models, updateProvider, provider, assistants, defaultModel?.id, defaultModel?.provider, dispatch, setDefaultModel]
|
||||||
|
)
|
||||||
updateProvider({ ...provider, models: updatedModels })
|
|
||||||
|
|
||||||
// Update assistants using this model
|
|
||||||
assistants.forEach((assistant) => {
|
|
||||||
if (assistant?.model?.id === updatedModel.id && assistant.model.provider === provider.id) {
|
|
||||||
dispatch(
|
|
||||||
setModel({
|
|
||||||
assistantId: assistant.id,
|
|
||||||
model: updatedModel
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update default model if needed
|
|
||||||
if (defaultModel?.id === updatedModel.id && defaultModel?.provider === provider.id) {
|
|
||||||
setDefaultModel(updatedModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -396,4 +400,4 @@ const ModelLatencyText = styled(Typography.Text)`
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
`
|
`
|
||||||
|
|
||||||
export default ModelList
|
export default memo(ModelList)
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { providerCharge } from '@renderer/utils/oauth'
|
|||||||
import { Button, Divider, Flex, Input, Space, Switch, Tooltip } from 'antd'
|
import { Button, Divider, Flex, Input, Space, Switch, Tooltip } from 'antd'
|
||||||
import Link from 'antd/es/typography/Link'
|
import Link from 'antd/es/typography/Link'
|
||||||
import { debounce, isEmpty } from 'lodash'
|
import { debounce, isEmpty } from 'lodash'
|
||||||
import { FC, useCallback, useEffect, useState } from 'react'
|
import { FC, useCallback, useDeferredValue, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -51,7 +51,8 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
||||||
const [apiValid, setApiValid] = useState(false)
|
const [apiValid, setApiValid] = useState(false)
|
||||||
const [apiChecking, setApiChecking] = useState(false)
|
const [apiChecking, setApiChecking] = useState(false)
|
||||||
const [searchText, setSearchText] = useState('')
|
const [modelSearchText, setModelSearchText] = useState('')
|
||||||
|
const deferredModelSearchText = useDeferredValue(modelSearchText)
|
||||||
const { updateProvider, models } = useProvider(provider.id)
|
const { updateProvider, models } = useProvider(provider.id)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
@ -387,7 +388,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
<Space align="center" style={{ width: '100%', justifyContent: 'space-between' }}>
|
<Space align="center" style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||||
<Space>
|
<Space>
|
||||||
<span>{t('common.models')}</span>
|
<span>{t('common.models')}</span>
|
||||||
{!isEmpty(models) && <ModelListSearchBar onSearch={setSearchText} />}
|
{!isEmpty(models) && <ModelListSearchBar onSearch={setModelSearchText} />}
|
||||||
</Space>
|
</Space>
|
||||||
{!isEmpty(models) && (
|
{!isEmpty(models) && (
|
||||||
<Tooltip title={t('settings.models.check.button_caption')} mouseEnterDelay={0.5}>
|
<Tooltip title={t('settings.models.check.button_caption')} mouseEnterDelay={0.5}>
|
||||||
@ -402,7 +403,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</SettingSubtitle>
|
</SettingSubtitle>
|
||||||
<ModelList provider={provider} modelStatuses={modelStatuses} searchText={searchText} />
|
<ModelList providerId={provider.id} modelStatuses={modelStatuses} searchText={deferredModelSearchText} />
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user