mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 13:59:28 +08:00
perf: improve model list loading (#8751)
* refactor(ModelList): use spin as a wrapper * perf: improve SvgSpinners180Ring
This commit is contained in:
parent
488a01d7d7
commit
e2b13ade95
@ -1,20 +1,41 @@
|
|||||||
import { SVGProps } from 'react'
|
import { SVGProps } from 'react'
|
||||||
|
|
||||||
export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement>) {
|
export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement>) {
|
||||||
|
// 避免与全局样式冲突
|
||||||
|
const animationClassName = 'svg-spinner-anim-180-ring'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
<>
|
||||||
{/* Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE */}
|
{/* CSS transform 硬件加速 */}
|
||||||
<path
|
<style>
|
||||||
fill="currentColor"
|
{`
|
||||||
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z">
|
@keyframes svg-spinner-rotate-180-ring {
|
||||||
<animateTransform
|
from {
|
||||||
attributeName="transform"
|
transform: rotate(0deg);
|
||||||
dur="0.75s"
|
}
|
||||||
repeatCount="indefinite"
|
to {
|
||||||
type="rotate"
|
transform: rotate(360deg);
|
||||||
values="0 12 12;360 12 12"></animateTransform>
|
}
|
||||||
</path>
|
}
|
||||||
</svg>
|
.${animationClassName} {
|
||||||
|
transform-origin: center;
|
||||||
|
animation: svg-spinner-rotate-180-ring 0.75s linear infinite;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
className={`${animationClassName} ${props.className || ''}`.trim()}>
|
||||||
|
{/* Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE */}
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"></path>
|
||||||
|
</svg>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default SvgSpinners180Ring
|
export default SvgSpinners180Ring
|
||||||
|
|||||||
@ -16,8 +16,8 @@ import { useAppDispatch } from '@renderer/store'
|
|||||||
import { setModel } from '@renderer/store/assistants'
|
import { setModel } from '@renderer/store/assistants'
|
||||||
import { Model } from '@renderer/types'
|
import { Model } from '@renderer/types'
|
||||||
import { filterModelsByKeywords } from '@renderer/utils'
|
import { filterModelsByKeywords } from '@renderer/utils'
|
||||||
import { Button, Flex, Spin, Tooltip } from 'antd'
|
import { Button, Empty, Flex, Spin, Tooltip } from 'antd'
|
||||||
import { groupBy, sortBy, toPairs } from 'lodash'
|
import { groupBy, isEmpty, sortBy, toPairs } from 'lodash'
|
||||||
import { ListCheck, Plus } from 'lucide-react'
|
import { ListCheck, Plus } from 'lucide-react'
|
||||||
import React, { memo, startTransition, useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { memo, startTransition, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -134,6 +134,8 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
|
|||||||
[provider, onUpdateModel]
|
[provider, onUpdateModel]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isLoading = useMemo(() => displayedModelGroups === null, [displayedModelGroups])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingSubtitle style={{ marginBottom: 5 }}>
|
<SettingSubtitle style={{ marginBottom: 5 }}>
|
||||||
@ -158,54 +160,60 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
</SettingSubtitle>
|
</SettingSubtitle>
|
||||||
{displayedModelGroups === null ? (
|
<Spin spinning={isLoading} indicator={<SvgSpinners180Ring color="var(--color-text-2)" />}>
|
||||||
<Flex align="center" justify="center" style={{ minHeight: '8rem' }}>
|
{displayedModelGroups && !isEmpty(displayedModelGroups) ? (
|
||||||
<Spin indicator={<SvgSpinners180Ring color="var(--color-text-2)" />} />
|
<Flex gap={12} vertical>
|
||||||
|
{Object.keys(displayedModelGroups).map((group, i) => (
|
||||||
|
<ModelListGroup
|
||||||
|
key={group}
|
||||||
|
groupName={group}
|
||||||
|
models={displayedModelGroups[group]}
|
||||||
|
modelStatuses={modelStatuses}
|
||||||
|
defaultOpen={i <= 5}
|
||||||
|
disabled={isHealthChecking}
|
||||||
|
onEditModel={onEditModel}
|
||||||
|
onRemoveModel={removeModel}
|
||||||
|
onRemoveGroup={() => displayedModelGroups[group].forEach((model) => removeModel(model))}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
description={t('settings.models.empty')}
|
||||||
|
style={{ visibility: isLoading ? 'hidden' : 'visible' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Spin>
|
||||||
|
<Flex justify="space-between" align="center">
|
||||||
|
{docsWebsite || modelsWebsite ? (
|
||||||
|
<SettingHelpTextRow>
|
||||||
|
<SettingHelpText>{t('settings.provider.docs_check')} </SettingHelpText>
|
||||||
|
{docsWebsite && (
|
||||||
|
<SettingHelpLink target="_blank" href={docsWebsite}>
|
||||||
|
{getProviderLabel(provider.id) + ' '}
|
||||||
|
{t('common.docs')}
|
||||||
|
</SettingHelpLink>
|
||||||
|
)}
|
||||||
|
{docsWebsite && modelsWebsite && <SettingHelpText>{t('common.and')}</SettingHelpText>}
|
||||||
|
{modelsWebsite && (
|
||||||
|
<SettingHelpLink target="_blank" href={modelsWebsite}>
|
||||||
|
{t('common.models')}
|
||||||
|
</SettingHelpLink>
|
||||||
|
)}
|
||||||
|
<SettingHelpText>{t('settings.provider.docs_more_details')}</SettingHelpText>
|
||||||
|
</SettingHelpTextRow>
|
||||||
|
) : (
|
||||||
|
<div style={{ height: 5 }} />
|
||||||
|
)}
|
||||||
|
<Flex gap={10} style={{ marginTop: 12 }}>
|
||||||
|
<Button type="primary" onClick={onManageModel} icon={<ListCheck size={16} />} disabled={isHealthChecking}>
|
||||||
|
{t('button.manage')}
|
||||||
|
</Button>
|
||||||
|
<Button type="default" onClick={onAddModel} icon={<Plus size={16} />} disabled={isHealthChecking}>
|
||||||
|
{t('button.add')}
|
||||||
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
) : (
|
|
||||||
<Flex gap={12} vertical>
|
|
||||||
{Object.keys(displayedModelGroups).map((group, i) => (
|
|
||||||
<ModelListGroup
|
|
||||||
key={group}
|
|
||||||
groupName={group}
|
|
||||||
models={displayedModelGroups[group]}
|
|
||||||
modelStatuses={modelStatuses}
|
|
||||||
defaultOpen={i <= 5}
|
|
||||||
disabled={isHealthChecking}
|
|
||||||
onEditModel={onEditModel}
|
|
||||||
onRemoveModel={removeModel}
|
|
||||||
onRemoveGroup={() => displayedModelGroups[group].forEach((model) => removeModel(model))}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{docsWebsite || modelsWebsite ? (
|
|
||||||
<SettingHelpTextRow>
|
|
||||||
<SettingHelpText>{t('settings.provider.docs_check')} </SettingHelpText>
|
|
||||||
{docsWebsite && (
|
|
||||||
<SettingHelpLink target="_blank" href={docsWebsite}>
|
|
||||||
{getProviderLabel(provider.id) + ' '}
|
|
||||||
{t('common.docs')}
|
|
||||||
</SettingHelpLink>
|
|
||||||
)}
|
|
||||||
{docsWebsite && modelsWebsite && <SettingHelpText>{t('common.and')}</SettingHelpText>}
|
|
||||||
{modelsWebsite && (
|
|
||||||
<SettingHelpLink target="_blank" href={modelsWebsite}>
|
|
||||||
{t('common.models')}
|
|
||||||
</SettingHelpLink>
|
|
||||||
)}
|
|
||||||
<SettingHelpText>{t('settings.provider.docs_more_details')}</SettingHelpText>
|
|
||||||
</SettingHelpTextRow>
|
|
||||||
) : (
|
|
||||||
<div style={{ height: 5 }} />
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
<Flex gap={10} style={{ marginTop: 10 }}>
|
|
||||||
<Button type="primary" onClick={onManageModel} icon={<ListCheck size={16} />} disabled={isHealthChecking}>
|
|
||||||
{t('button.manage')}
|
|
||||||
</Button>
|
|
||||||
<Button type="default" onClick={onAddModel} icon={<Plus size={16} />} disabled={isHealthChecking}>
|
|
||||||
{t('button.add')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user