feat(provider-settings): add option to hide disabled providers

- Add hide disabled providers toggle in provider settings
- Update preference schema to store the setting
- Refactor provider list layout to accommodate new toggle
This commit is contained in:
icarus 2025-10-24 04:10:02 +08:00
parent 736aef22c4
commit b6fa2583d1
5 changed files with 45 additions and 19 deletions

View File

@ -47,6 +47,7 @@ export interface PreferenceSchemas {
'app.proxy.mode': PreferenceTypes.ProxyMode
// redux/settings/proxyUrl
'app.proxy.url': string
'app.settings.provider.hide_disabled': boolean
// redux/settings/enableSpellCheck
'app.spell_check.enabled': boolean
// redux/settings/spellCheckLanguages
@ -433,6 +434,7 @@ export const DefaultPreferences: PreferenceSchemas = {
'app.proxy.bypass_rules': '',
'app.proxy.mode': 'system',
'app.proxy.url': '',
'app.settings.provider.hide_disabled': false,
'app.spell_check.enabled': false,
'app.spell_check.languages': [],
'app.tray.enabled': true,

View File

@ -4176,6 +4176,11 @@
"docs_check": "Check",
"docs_more_details": "for more details",
"get_api_key": "Get API Key",
"list": {
"settings": {
"hide_disabled": "Hide disabled providers"
}
},
"misc": "Other",
"no_models_for_check": "No models available for checking (e.g. chat models)",
"not_checked": "Not Checked",

View File

@ -1,4 +1,5 @@
import { Button } from '@cherrystudio/ui'
import { Button, Popover, PopoverContent, PopoverTrigger, Switch } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import type { DropResult } from '@hello-pangea/dnd'
import { loggerService } from '@logger'
import {
@ -16,7 +17,7 @@ import { isSystemProvider } from '@renderer/types'
import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils'
import type { MenuProps } from 'antd'
import { Dropdown, Input, Tag } from 'antd'
import { GripVertical, PlusIcon, Search, UserPen } from 'lucide-react'
import { GripVertical, PlusIcon, Search, SettingsIcon, UserPen } from 'lucide-react'
import type { FC } from 'react'
import { startTransition, useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -30,7 +31,6 @@ import UrlSchemaInfoPopup from './UrlSchemaInfoPopup'
const logger = loggerService.withContext('ProviderList')
const BUTTON_WRAPPER_HEIGHT = 50
const systemType = await window.api.system.getDeviceType()
const cpuName = await window.api.system.getCpuName()
@ -45,6 +45,7 @@ const ProviderList: FC = () => {
const [dragging, setDragging] = useState(false)
const [providerLogos, setProviderLogos] = useState<Record<string, string>>({})
const listRef = useRef<DraggableVirtualListRef>(null)
const [hideDisabled, setHideDisabled] = usePreference('app.settings.provider.hide_disabled')
const setSelectedProvider = useCallback((provider: Provider) => {
startTransition(() => _setSelectedProvider(provider))
@ -283,6 +284,10 @@ const ProviderList: FC = () => {
return false
}
if (hideDisabled && !provider.enabled) {
return false
}
const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean)
const isProviderMatch = matchKeywordsInProvider(keywords, provider)
const isModelMatch = provider.models.some((model) => matchKeywordsInModel(keywords, model))
@ -311,12 +316,12 @@ const ProviderList: FC = () => {
return (
<Container className="selectable">
<ProviderListContainer>
<AddButtonWrapper>
<header className="flex h-11 gap-1 p-1">
<Input
type="text"
placeholder={t('settings.provider.search')}
value={searchText}
style={{ borderRadius: 'var(--list-item-border-radius)', height: 35 }}
style={{ borderRadius: 'var(--list-item-border-radius)' }}
suffix={<Search size={14} />}
onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => {
@ -328,7 +333,29 @@ const ProviderList: FC = () => {
allowClear
disabled={dragging}
/>
</AddButtonWrapper>
<Popover>
<PopoverTrigger asChild>
<Button
variant="light"
startContent={<SettingsIcon size={18} />}
isIconOnly
className="h-full min-h-0 w-9 min-w-0"
/>
</PopoverTrigger>
<PopoverContent>
<ul className="flex h-full w-full flex-col">
<li className="flex flex-row items-center justify-between">
<Switch
isSelected={hideDisabled}
onValueChange={setHideDisabled}
classNames={{ base: 'flex-1 flex-row-reverse justify-between max-w-full' }}>
{t('settings.provider.list.settings.hide_disabled')}
</Switch>
</li>
</ul>
</PopoverContent>
</Popover>
</header>
<DraggableVirtualList
ref={listRef}
list={filteredProviders}
@ -338,7 +365,8 @@ const ProviderList: FC = () => {
itemKey={itemKey}
overscan={3}
style={{
height: `calc(100% - 2 * ${BUTTON_WRAPPER_HEIGHT}px)`
flex: 1,
overflow: 'hidden'
}}
scrollerStyle={{
padding: 8,
@ -372,7 +400,7 @@ const ProviderList: FC = () => {
</Dropdown>
)}
</DraggableVirtualList>
<AddButtonWrapper>
<footer className="h-12">
<Button
size="sm"
style={{ width: '100%', borderRadius: 'var(--list-item-border-radius)' }}
@ -381,7 +409,7 @@ const ProviderList: FC = () => {
isDisabled={dragging}>
{t('button.add')}
</Button>
</AddButtonWrapper>
</footer>
</ProviderListContainer>
<ProviderSetting providerId={selectedProvider.id} key={selectedProvider.id} />
</Container>
@ -451,12 +479,4 @@ const ProviderItemName = styled.div`
font-weight: 500;
`
const AddButtonWrapper = styled.div`
height: ${BUTTON_WRAPPER_HEIGHT}px;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 10px 8px;
`
export default ProviderList

View File

@ -1 +0,0 @@
export { default as ProviderList } from './ProviderList'

View File

@ -33,7 +33,7 @@ import GeneralSettings from './GeneralSettings'
import MCPSettings from './MCPSettings'
import MemorySettings from './MemorySettings'
import NotesSettings from './NotesSettings'
import { ProviderList } from './ProviderSettings'
import ProviderList from './ProviderSettings/ProviderList'
import QuickAssistantSettings from './QuickAssistantSettings'
import QuickPhraseSettings from './QuickPhraseSettings'
import SelectionAssistantSettings from './SelectionAssistantSettings/SelectionAssistantSettings'