feat(ProviderSettings): resizable provider settings (#9004)

* feat(ProviderSettings): 添加Splitter组件实现左右面板布局

移除固定的最小宽度限制,使用Splitter组件实现可调整的左右面板布局

* style(ProviderSetting): 优化提供商名称显示样式

将 ProviderName 组件从 span 改为 Typography.Text 以支持文本溢出省略
添加 flex 布局属性确保标题区域正确布局

* feat(ProviderSetting): 添加中间省略文本组件并优化HostPreview显示

在ProviderSetting页面中引入EllipsisMiddle组件,用于处理长文本的中间省略显示
重构hostPreview为HostPreview组件,使用EllipsisMiddle优化长URL的展示效果

* fix(ProviderSettings): 修复Splitter.Panel默认大小未设置问题

* Revert "feat(ProviderSetting): 添加中间省略文本组件并优化HostPreview显示"

This reverts commit bfbba8f5db.

* refactor: improve splitter style

* refactor: improve dragger divider size

---------

Co-authored-by: one <wangan.cs@gmail.com>
This commit is contained in:
Phantom 2025-08-11 14:09:45 +08:00 committed by GitHub
parent 46c247149e
commit 5713a278cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 104 additions and 71 deletions

View File

@ -184,3 +184,26 @@
box-shadow: 0 1px 4px 0px rgb(128 128 128 / 50%) !important;
}
}
.ant-splitter-bar {
.ant-splitter-bar-dragger {
&::before {
background-color: var(--color-border) !important;
transition: background-color 0.15s ease-in-out;
}
&:hover {
&::before {
width: 4px !important;
background-color: var(--color-primary) !important;
transition: background-color 0.15s ease-in-out;
}
}
}
.ant-splitter-bar-dragger-active {
&::before {
width: 4px !important;
background-color: var(--color-primary) !important;
}
}
}

View File

@ -99,6 +99,11 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
},
Divider: {
colorSplit: 'rgba(128,128,128,0.15)'
},
Splitter: {
splitBarDraggableSize: 0,
splitBarSize: 0.5,
splitTriggerSize: 10
}
},
token: {

View File

@ -13,7 +13,7 @@ import { isProviderSupportAuth } from '@renderer/services/ProviderService'
import { ApiKeyConnectivity, HealthStatus } from '@renderer/types/healthCheck'
import { formatApiHost, formatApiKeys, getFancyProviderName, isOpenAIProvider } from '@renderer/utils'
import { formatErrorMessage } from '@renderer/utils/error'
import { Button, Divider, Flex, Input, Space, Switch, Tooltip } from 'antd'
import { Button, Divider, Flex, Input, Space, Switch, Tooltip, Typography } from 'antd'
import Link from 'antd/es/typography/Link'
import { debounce, isEmpty } from 'lodash'
import { Check, Settings2, SquareArrowOutUpRight, TriangleAlert } from 'lucide-react'
@ -229,8 +229,8 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
return (
<SettingContainer theme={theme} style={{ background: 'var(--color-background)' }}>
<SettingTitle>
<Flex align="center" gap={5}>
<ProviderName>{fancyProviderName}</ProviderName>
<Flex align="center" gap={5} flex={1} style={{ overflow: 'hidden' }}>
<ProviderName ellipsis>{fancyProviderName}</ProviderName>
{officialWebsite && (
<Link target="_blank" href={providerConfig.websites.official} style={{ display: 'flex' }}>
<Button type="text" size="small" icon={<SquareArrowOutUpRight size={14} />} />
@ -372,7 +372,7 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
)
}
const ProviderName = styled.span`
const ProviderName = styled(Typography.Text)`
font-size: 14px;
font-weight: 500;
margin-right: -2px;

View File

@ -15,7 +15,7 @@ import {
matchKeywordsInProvider,
uuid
} from '@renderer/utils'
import { Avatar, Button, Card, Dropdown, Input, MenuProps, Tag } from 'antd'
import { Avatar, Button, Card, Dropdown, Input, MenuProps, Splitter, Tag } from 'antd'
import { Eye, EyeOff, GripVertical, PlusIcon, Search, UserPen } from 'lucide-react'
import { FC, startTransition, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -455,70 +455,77 @@ const ProvidersList: FC = () => {
return (
<Container className="selectable">
<ProviderListContainer>
<AddButtonWrapper>
<Input
type="text"
placeholder={t('settings.provider.search')}
value={searchText}
style={{ borderRadius: 'var(--list-item-border-radius)', height: 35 }}
suffix={<Search size={14} />}
onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setSearchText('')
}
}}
allowClear
disabled={dragging}
/>
</AddButtonWrapper>
<DraggableVirtualList
list={filteredProviders}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
estimateSize={useCallback(() => 40, [])}
itemKey={itemKey}
overscan={3}
style={{
height: `calc(100% - 2 * ${BUTTON_WRAPPER_HEIGHT}px)`
}}
scrollerStyle={{
padding: 8,
paddingRight: 5
}}
itemContainerStyle={{ paddingBottom: 5 }}>
{(provider) => (
<Dropdown menu={{ items: getDropdownMenus(provider) }} trigger={['contextMenu']}>
<ProviderListItem
key={provider.id}
className={provider.id === selectedProvider?.id ? 'active' : ''}
onClick={() => setSelectedProvider(provider)}>
<DragHandle>
<GripVertical size={12} />
</DragHandle>
{getProviderAvatar(provider)}
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
{provider.enabled && (
<Tag color="green" style={{ marginLeft: 'auto', marginRight: 0, borderRadius: 16 }}>
ON
</Tag>
)}
</ProviderListItem>
</Dropdown>
)}
</DraggableVirtualList>
<AddButtonWrapper>
<Button
style={{ width: '100%', borderRadius: 'var(--list-item-border-radius)' }}
icon={<PlusIcon size={16} />}
onClick={onAddProvider}
disabled={dragging}>
{t('button.add')}
</Button>
</AddButtonWrapper>
</ProviderListContainer>
<ProviderSetting providerId={selectedProvider.id} key={selectedProvider.id} />
<Splitter>
<Splitter.Panel min={250} defaultSize={250}>
<ProviderListContainer>
<AddButtonWrapper>
<Input
type="text"
placeholder={t('settings.provider.search')}
value={searchText}
style={{ borderRadius: 'var(--list-item-border-radius)', height: 35 }}
suffix={<Search size={14} />}
onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setSearchText('')
}
}}
allowClear
disabled={dragging}
/>
</AddButtonWrapper>
<DraggableVirtualList
list={filteredProviders}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
estimateSize={useCallback(() => 40, [])}
itemKey={itemKey}
overscan={3}
style={{
height: `calc(100% - 2 * ${BUTTON_WRAPPER_HEIGHT}px)`
}}
scrollerStyle={{
padding: 8,
paddingRight: 5
}}
itemContainerStyle={{ paddingBottom: 5 }}>
{(provider) => (
<Dropdown menu={{ items: getDropdownMenus(provider) }} trigger={['contextMenu']}>
<ProviderListItem
key={provider.id}
className={provider.id === selectedProvider?.id ? 'active' : ''}
onClick={() => setSelectedProvider(provider)}>
<DragHandle>
<GripVertical size={12} />
</DragHandle>
{getProviderAvatar(provider)}
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
{provider.enabled && (
<Tag color="green" style={{ marginLeft: 'auto', marginRight: 0, borderRadius: 16 }}>
ON
</Tag>
)}
</ProviderListItem>
</Dropdown>
)}
</DraggableVirtualList>
<AddButtonWrapper>
<Button
style={{ width: '100%', borderRadius: 'var(--list-item-border-radius)' }}
icon={<PlusIcon size={16} />}
onClick={onAddProvider}
disabled={dragging}>
{t('button.add')}
</Button>
</AddButtonWrapper>
</ProviderListContainer>
</Splitter.Panel>
<Splitter.Panel min={'50%'}>
<ProviderSetting providerId={selectedProvider.id} key={selectedProvider.id} />
</Splitter.Panel>
</Splitter>
</Container>
)
}
@ -533,9 +540,7 @@ const Container = styled.div`
const ProviderListContainer = styled.div`
display: flex;
flex-direction: column;
min-width: calc(var(--settings-width) + 10px);
height: calc(100vh - var(--navbar-height));
border-right: 0.5px solid var(--color-border);
`
const ProviderListItem = styled.div`