refactor(ModelList): improve group style (#8761)

* refactor(ModelList): 重构模型列表组件结构,优化分组渲染逻辑

将扁平化列表结构改为嵌套结构,提升分组模型的渲染性能
移除不必要的状态依赖,简化组件逻辑
添加分组容器样式,改善视觉呈现

* Revert "refactor(ModelList): 重构模型列表组件结构,优化分组渲染逻辑"

This reverts commit f60f6267e6.

* refactor(ModelList): 优化模型列表的渲染和样式

- 使用startTransition优化折叠/展开性能
- 重构数据结构,将单个模型渲染改为批量渲染
- 改进组头和模型项的样式和布局

* Revert "refactor(ModelList): 优化模型列表的渲染和样式"

This reverts commit e18286c70e.

* feat(模型列表): 优化模型列表项的样式和分组显示

添加last属性标记列表最后一项,优化分组标题和列表项的样式
移除多余的底部间距,调整边框圆角以提升视觉一致性

* refactor: 移除调试用的console.log语句

* style(ManageModelsList): 调整分组标题的内边距以改善视觉间距

* style(ModelList): 移动按钮位置

* style(ManageModelsList): 调整列表项和分组标题的高度以优化空间使用

* style(ManageModelsList): 为滚动容器添加圆角边框样式
This commit is contained in:
Phantom 2025-08-03 11:00:04 +08:00 committed by GitHub
parent fb2dccc7ff
commit 63198ee3d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 85 additions and 56 deletions

View File

@ -26,6 +26,7 @@ interface GroupRowData {
interface ModelRowData { interface ModelRowData {
type: 'model' type: 'model'
model: Model model: Model
last?: boolean
} }
type RowData = GroupRowData | ModelRowData type RowData = GroupRowData | ModelRowData
@ -62,9 +63,16 @@ 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(
rows.push({ type: 'model', model }) ...models.map(
}) (model, index) =>
({
type: 'model',
model,
last: index === models.length - 1 ? true : undefined
}) as const
)
)
} }
} }
}) })
@ -131,37 +139,41 @@ const ManageModelsList: React.FC<ManageModelsListProps> = ({ modelGroups, provid
isSticky={useCallback((index: number) => flatRows[index].type === 'group', [flatRows])} isSticky={useCallback((index: number) => flatRows[index].type === 'group', [flatRows])}
overscan={5} overscan={5}
scrollerStyle={{ scrollerStyle={{
paddingRight: '10px' paddingRight: '10px',
}} borderRadius: '8px'
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>
<span style={{ fontWeight: 'bold', fontSize: '14px' }}>{row.groupName}</span> <CustomTag color="#02B96B" size={10}>
<CustomTag color="#02B96B" size={10}> {row.models.length}
{row.models.length} </CustomTag>
</CustomTag> </Flex>
</Flex> {renderGroupTools(row.models)}
{renderGroupTools(row.models)} </GroupHeader>
</GroupHeader> </GroupHeaderContainer>
) )
} }
return ( return (
<ModelListItem model={row.model} provider={provider} onAddModel={onAddModel} onRemoveModel={onRemoveModel} /> <ModelListItem
last={row.last}
model={row.model}
provider={provider}
onAddModel={onAddModel}
onRemoveModel={onRemoveModel}
/>
) )
}} }}
</DynamicVirtualList> </DynamicVirtualList>
@ -174,41 +186,58 @@ interface ModelListItemProps {
provider: Provider provider: Provider
onAddModel: (model: Model) => void onAddModel: (model: Model) => void
onRemoveModel: (model: Model) => void onRemoveModel: (model: Model) => void
last?: boolean
} }
const ModelListItem: React.FC<ModelListItemProps> = memo(({ model, provider, onAddModel, onRemoveModel }) => { const ModelListItem: React.FC<ModelListItemProps> = memo(({ model, provider, onAddModel, onRemoveModel, last }) => {
const isAdded = useMemo(() => isModelInProvider(provider, model.id), [provider, model.id]) const isAdded = useMemo(() => isModelInProvider(provider, model.id), [provider, model.id])
return ( return (
<FileItem <ModelListItemContainer last={last}>
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 />} />
}} )
/> }}
/>
</ModelListItemContainer>
) )
}) })
const GroupHeader = styled.div` const GroupHeader = styled.div<{ isCollapsed: boolean }>`
display: flex; display: flex;
background-color: var(--color-background-mute);
border-radius: ${(props) => (props.isCollapsed ? '8px' : '8px 8px 0 0')};
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 8px; padding: 0 13px;
min-height: 50px; min-height: 35px;
color: var(--color-text); color: var(--color-text);
cursor: pointer; cursor: pointer;
` `
const GroupHeaderContainer = styled.div<{ isCollapsed: boolean }>`
padding-bottom: ${(props) => (props.isCollapsed ? '8px' : '0')};
`
const ModelListItemContainer = styled.div<{ last?: boolean }>`
border: 1px solid var(--color-border);
padding: 4px;
border-top: none;
border-radius: ${(props) => (props.last ? '0 0 8px 8px' : '0')};
border-bottom: ${(props) => (props.last ? '1px solid var(--color-border)' : 'none')};
margin-bottom: ${(props) => (props.last ? '8px' : '0')};
`
export default memo(ManageModelsList) export default memo(ManageModelsList)

View File

@ -206,14 +206,14 @@ const ModelList: React.FC<ModelListProps> = ({ providerId }) => {
) : ( ) : (
<div style={{ height: 5 }} /> <div style={{ height: 5 }} />
)} )}
<Flex gap={10} style={{ marginTop: 12 }}> </Flex>
<Button type="primary" onClick={onManageModel} icon={<ListCheck size={16} />} disabled={isHealthChecking}> <Flex gap={10} style={{ marginTop: 12 }}>
{t('button.manage')} <Button type="primary" onClick={onManageModel} icon={<ListCheck size={16} />} disabled={isHealthChecking}>
</Button> {t('button.manage')}
<Button type="default" onClick={onAddModel} icon={<Plus size={16} />} disabled={isHealthChecking}> </Button>
{t('button.add')} <Button type="default" onClick={onAddModel} icon={<Plus size={16} />} disabled={isHealthChecking}>
</Button> {t('button.add')}
</Flex> </Button>
</Flex> </Flex>
</> </>
) )