mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 05:09:09 +08:00
refactor(ModelList): 优化模型列表的渲染和样式
- 使用startTransition优化折叠/展开性能 - 重构数据结构,将单个模型渲染改为批量渲染 - 改进组头和模型项的样式和布局
This commit is contained in:
parent
f8c471105e
commit
e18286c70e
@ -10,7 +10,7 @@ import { Model, Provider } from '@renderer/types'
|
|||||||
import { Button, Flex, Tooltip } from 'antd'
|
import { Button, Flex, Tooltip } from 'antd'
|
||||||
import { Avatar } from 'antd'
|
import { Avatar } from 'antd'
|
||||||
import { ChevronRight } from 'lucide-react'
|
import { ChevronRight } from 'lucide-react'
|
||||||
import React, { memo, useCallback, useMemo, useState } from 'react'
|
import React, { memo, startTransition, 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'
|
||||||
|
|
||||||
@ -23,12 +23,12 @@ interface GroupRowData {
|
|||||||
models: Model[]
|
models: Model[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ModelRowData {
|
interface ModelsRowData {
|
||||||
type: 'model'
|
type: 'models'
|
||||||
model: Model
|
models: Model[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type RowData = GroupRowData | ModelRowData
|
type RowData = GroupRowData | ModelsRowData
|
||||||
|
|
||||||
interface ManageModelsListProps {
|
interface ManageModelsListProps {
|
||||||
modelGroups: Record<string, Model[]>
|
modelGroups: Record<string, Model[]>
|
||||||
@ -42,14 +42,16 @@ const ManageModelsList: React.FC<ManageModelsListProps> = ({ modelGroups, provid
|
|||||||
const [collapsedGroups, setCollapsedGroups] = useState(new Set<string>())
|
const [collapsedGroups, setCollapsedGroups] = useState(new Set<string>())
|
||||||
|
|
||||||
const handleGroupToggle = useCallback((groupName: string) => {
|
const handleGroupToggle = useCallback((groupName: string) => {
|
||||||
setCollapsedGroups((prev) => {
|
startTransition(() => {
|
||||||
const newSet = new Set(prev)
|
setCollapsedGroups((prev) => {
|
||||||
if (newSet.has(groupName)) {
|
const newSet = new Set(prev)
|
||||||
newSet.delete(groupName) // 如果已折叠,则展开
|
if (newSet.has(groupName)) {
|
||||||
} else {
|
newSet.delete(groupName) // 如果已折叠,则展开
|
||||||
newSet.add(groupName) // 如果已展开,则折叠
|
} else {
|
||||||
}
|
newSet.add(groupName) // 如果已展开,则折叠
|
||||||
return newSet
|
}
|
||||||
|
return newSet
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -62,9 +64,7 @@ const ManageModelsList: React.FC<ManageModelsListProps> = ({ modelGroups, provid
|
|||||||
// 只添加非空组
|
// 只添加非空组
|
||||||
rows.push({ type: 'group', groupName, models })
|
rows.push({ type: 'group', groupName, models })
|
||||||
if (!collapsedGroups.has(groupName)) {
|
if (!collapsedGroups.has(groupName)) {
|
||||||
models.forEach((model) => {
|
rows.push({ type: 'models', models })
|
||||||
rows.push({ type: 'model', model })
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -132,37 +132,44 @@ const ManageModelsList: React.FC<ManageModelsListProps> = ({ modelGroups, provid
|
|||||||
overscan={5}
|
overscan={5}
|
||||||
scrollerStyle={{
|
scrollerStyle={{
|
||||||
paddingRight: '10px'
|
paddingRight: '10px'
|
||||||
}}
|
|
||||||
itemContainerStyle={{
|
|
||||||
paddingBottom: '8px'
|
|
||||||
}}>
|
}}>
|
||||||
{(row) => {
|
{(row) => {
|
||||||
if (row.type === 'group') {
|
if (row.type === 'group') {
|
||||||
const isCollapsed = collapsedGroups.has(row.groupName)
|
const isCollapsed = collapsedGroups.has(row.groupName)
|
||||||
return (
|
return (
|
||||||
<GroupHeader
|
<GroupHeaderContainer isCollapsed={isCollapsed}>
|
||||||
style={{ background: 'var(--color-background)' }}
|
<GroupHeader isCollapsed={isCollapsed} onClick={() => handleGroupToggle(row.groupName)}>
|
||||||
onClick={() => handleGroupToggle(row.groupName)}>
|
<Flex align="center" gap={10} style={{ flex: 1 }}>
|
||||||
<Flex align="center" gap={10} style={{ flex: 1 }}>
|
<ChevronRight
|
||||||
<ChevronRight
|
size={16}
|
||||||
size={16}
|
color="var(--color-text-3)"
|
||||||
color="var(--color-text-3)"
|
strokeWidth={1.5}
|
||||||
strokeWidth={1.5}
|
style={{ transform: isCollapsed ? 'rotate(0deg)' : 'rotate(90deg)' }}
|
||||||
style={{ transform: isCollapsed ? 'rotate(0deg)' : 'rotate(90deg)' }}
|
/>
|
||||||
|
<span style={{ fontWeight: 'bold', fontSize: '14px' }}>{row.groupName}</span>
|
||||||
|
<CustomTag color="#02B96B" size={10}>
|
||||||
|
{row.models.length}
|
||||||
|
</CustomTag>
|
||||||
|
</Flex>
|
||||||
|
{renderGroupTools(row.models)}
|
||||||
|
</GroupHeader>
|
||||||
|
</GroupHeaderContainer>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<ModelsContainer>
|
||||||
|
{row.models.map((model) => (
|
||||||
|
<ModelListItem
|
||||||
|
key={model.id}
|
||||||
|
model={model}
|
||||||
|
provider={provider}
|
||||||
|
onAddModel={onAddModel}
|
||||||
|
onRemoveModel={onRemoveModel}
|
||||||
/>
|
/>
|
||||||
<span style={{ fontWeight: 'bold', fontSize: '14px' }}>{row.groupName}</span>
|
))}
|
||||||
<CustomTag color="#02B96B" size={10}>
|
</ModelsContainer>
|
||||||
{row.models.length}
|
|
||||||
</CustomTag>
|
|
||||||
</Flex>
|
|
||||||
{renderGroupTools(row.models)}
|
|
||||||
</GroupHeader>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<ModelListItem model={row.model} provider={provider} onAddModel={onAddModel} onRemoveModel={onRemoveModel} />
|
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
</DynamicVirtualList>
|
</DynamicVirtualList>
|
||||||
)
|
)
|
||||||
@ -180,35 +187,65 @@ const ModelListItem: React.FC<ModelListItemProps> = memo(({ model, provider, onA
|
|||||||
const isAdded = useMemo(() => isModelInProvider(provider, model.id), [provider, model.id])
|
const isAdded = useMemo(() => isModelInProvider(provider, model.id), [provider, model.id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileItem
|
<ModelItem>
|
||||||
style={{
|
<FileItem
|
||||||
backgroundColor: isAdded ? 'rgba(0, 126, 0, 0.06)' : '',
|
style={{
|
||||||
border: 'none',
|
backgroundColor: isAdded ? 'rgba(0, 126, 0, 0.06)' : '',
|
||||||
boxShadow: 'none'
|
border: 'none',
|
||||||
}}
|
boxShadow: 'none'
|
||||||
fileInfo={{
|
}}
|
||||||
icon: <Avatar src={getModelLogo(model.id)}>{model?.name?.[0]?.toUpperCase()}</Avatar>,
|
fileInfo={{
|
||||||
name: <ModelIdWithTags model={model} />,
|
icon: <Avatar src={getModelLogo(model.id)}>{model?.name?.[0]?.toUpperCase()}</Avatar>,
|
||||||
extra: model.description && <ExpandableText text={model.description} />,
|
name: <ModelIdWithTags model={model} />,
|
||||||
ext: '.model',
|
extra: model.description && <ExpandableText text={model.description} />,
|
||||||
actions: isAdded ? (
|
ext: '.model',
|
||||||
<Button type="text" onClick={() => onRemoveModel(model)} icon={<MinusOutlined />} />
|
actions: isAdded ? (
|
||||||
) : (
|
<Button type="text" onClick={() => onRemoveModel(model)} icon={<MinusOutlined />} />
|
||||||
<Button type="text" onClick={() => onAddModel(model)} icon={<PlusOutlined />} />
|
) : (
|
||||||
)
|
<Button type="text" onClick={() => onAddModel(model)} icon={<PlusOutlined />} />
|
||||||
}}
|
)
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</ModelItem>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const GroupHeader = styled.div`
|
const GroupHeaderContainer = styled.div<{ isCollapsed: boolean }>`
|
||||||
|
background-color: ${(props) => (props.isCollapsed ? 'transparent' : 'var(--color-background)')};
|
||||||
|
padding-bottom: ${(props) => (props.isCollapsed ? '8px' : '0')};
|
||||||
|
`
|
||||||
|
|
||||||
|
const GroupHeader = styled.div<{ isCollapsed: boolean }>`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background: var(--color-background-mute);
|
||||||
|
border-radius: ${(props) => (props.isCollapsed ? '1em' : '1em 1em 0 0')};
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-bottom: none;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ModelsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 1em 1em;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ModelItem = styled.div`
|
||||||
|
flex-direction: row;
|
||||||
|
position: relative;
|
||||||
|
border-radius: var(--list-item-border-radius);
|
||||||
|
border: 0.5px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default memo(ManageModelsList)
|
export default memo(ManageModelsList)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user