mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 15:49:29 +08:00
refactor(Select): provide consistent search experience for antd Select (#7363)
- Add CustomSelect for enhanced searching - Replace some Select components with CustomSelect - Fix translation language searching problem
This commit is contained in:
parent
5ca0ce682b
commit
c7a0b05841
17
src/renderer/src/components/CustomSelect.tsx
Normal file
17
src/renderer/src/components/CustomSelect.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { includeKeywords } from '@renderer/utils/search'
|
||||||
|
import { Select, SelectProps } from 'antd'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 Select,使用增强的搜索 filter
|
||||||
|
*/
|
||||||
|
const CustomSelect = ({ ref, ...props }: SelectProps & { ref?: React.RefObject<any | null> }) => {
|
||||||
|
return <Select ref={ref} filterOption={enhancedFilterOption} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomSelect.displayName = 'CustomSelect'
|
||||||
|
|
||||||
|
function enhancedFilterOption(input: string, option: any) {
|
||||||
|
return includeKeywords(option.label, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomSelect
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { RedoOutlined } from '@ant-design/icons'
|
import { RedoOutlined } from '@ant-design/icons'
|
||||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||||
|
import CustomSelect from '@renderer/components/CustomSelect'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
import PromptPopup from '@renderer/components/Popups/PromptPopup'
|
||||||
import { isEmbeddingModel } from '@renderer/config/models'
|
import { isEmbeddingModel } from '@renderer/config/models'
|
||||||
@ -96,7 +97,7 @@ const ModelSettings: FC = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
<Select
|
<CustomSelect
|
||||||
value={defaultModelValue}
|
value={defaultModelValue}
|
||||||
defaultValue={defaultModelValue}
|
defaultValue={defaultModelValue}
|
||||||
style={{ width: 360 }}
|
style={{ width: 360 }}
|
||||||
@ -118,7 +119,7 @@ const ModelSettings: FC = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
<Select
|
<CustomSelect
|
||||||
value={defaultTopicNamingModel}
|
value={defaultTopicNamingModel}
|
||||||
defaultValue={defaultTopicNamingModel}
|
defaultValue={defaultTopicNamingModel}
|
||||||
style={{ width: 360 }}
|
style={{ width: 360 }}
|
||||||
@ -140,7 +141,7 @@ const ModelSettings: FC = () => {
|
|||||||
</HStack>
|
</HStack>
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
<Select
|
<CustomSelect
|
||||||
value={defaultTranslateModel}
|
value={defaultTranslateModel}
|
||||||
defaultValue={defaultTranslateModel}
|
defaultValue={defaultTranslateModel}
|
||||||
style={{ width: 360 }}
|
style={{ width: 360 }}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { CheckOutlined, DeleteOutlined, HistoryOutlined, SendOutlined } from '@ant-design/icons'
|
import { CheckOutlined, DeleteOutlined, HistoryOutlined, SendOutlined } from '@ant-design/icons'
|
||||||
import { NavbarCenter, NavbarMain } from '@renderer/components/app/Navbar'
|
import { NavbarCenter, NavbarMain } from '@renderer/components/app/Navbar'
|
||||||
|
import CustomSelect from '@renderer/components/CustomSelect'
|
||||||
import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { isEmbeddingModel } from '@renderer/config/models'
|
import { isEmbeddingModel } from '@renderer/config/models'
|
||||||
import { translateLanguageOptions } from '@renderer/config/translate'
|
import { TranslateLanguageOptions } from '@renderer/config/translate'
|
||||||
import db from '@renderer/databases'
|
import db from '@renderer/databases'
|
||||||
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
import { useDefaultModel } from '@renderer/hooks/useAssistant'
|
||||||
import { useProviders } from '@renderer/hooks/useProvider'
|
import { useProviders } from '@renderer/hooks/useProvider'
|
||||||
@ -114,7 +115,7 @@ const TranslateSettings: FC<{
|
|||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: 8, fontWeight: 500 }}>{t('translate.settings.model')}</div>
|
<div style={{ marginBottom: 8, fontWeight: 500 }}>{t('translate.settings.model')}</div>
|
||||||
<HStack alignItems="center" gap={5}>
|
<HStack alignItems="center" gap={5}>
|
||||||
<Select
|
<CustomSelect
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
placeholder={t('translate.settings.model_placeholder')}
|
placeholder={t('translate.settings.model_placeholder')}
|
||||||
value={defaultTranslateModel}
|
value={defaultTranslateModel}
|
||||||
@ -174,38 +175,38 @@ const TranslateSettings: FC<{
|
|||||||
{isBidirectional && (
|
{isBidirectional && (
|
||||||
<Flex align="center" justify="space-between" gap={10}>
|
<Flex align="center" justify="space-between" gap={10}>
|
||||||
<Select
|
<Select
|
||||||
|
showSearch
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
value={localPair[0]}
|
value={localPair[0]}
|
||||||
|
optionFilterProp="label"
|
||||||
onChange={(value) => setLocalPair([value, localPair[1]])}
|
onChange={(value) => setLocalPair([value, localPair[1]])}
|
||||||
options={translateLanguageOptions().map((lang) => ({
|
options={TranslateLanguageOptions}
|
||||||
value: lang.value,
|
optionRender={(option) => (
|
||||||
label: (
|
<Space>
|
||||||
<Space.Compact direction="horizontal" block>
|
<span role="img" aria-label={(option.data as any).emoji}>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
{(option.data as any).emoji}
|
||||||
{lang.emoji}
|
</span>
|
||||||
</span>
|
{option.label}
|
||||||
<Space.Compact block>{lang.label}</Space.Compact>
|
</Space>
|
||||||
</Space.Compact>
|
)}
|
||||||
)
|
|
||||||
}))}
|
|
||||||
suffixIcon={<ChevronDown strokeWidth={1.5} size={16} color="var(--color-text-3)" />}
|
suffixIcon={<ChevronDown strokeWidth={1.5} size={16} color="var(--color-text-3)" />}
|
||||||
/>
|
/>
|
||||||
<span>⇆</span>
|
<span>⇆</span>
|
||||||
<Select
|
<Select
|
||||||
|
showSearch
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
value={localPair[1]}
|
value={localPair[1]}
|
||||||
|
optionFilterProp="label"
|
||||||
onChange={(value) => setLocalPair([localPair[0], value])}
|
onChange={(value) => setLocalPair([localPair[0], value])}
|
||||||
options={translateLanguageOptions().map((lang) => ({
|
options={TranslateLanguageOptions}
|
||||||
value: lang.value,
|
optionRender={(option) => (
|
||||||
label: (
|
<Space>
|
||||||
<Space.Compact direction="horizontal" block>
|
<span role="img" aria-label={(option.data as any).emoji}>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
{(option.data as any).emoji}
|
||||||
{lang.emoji}
|
</span>
|
||||||
</span>
|
{option.label}
|
||||||
<div style={{ textAlign: 'left', flex: 1 }}>{lang.label}</div>
|
</Space>
|
||||||
</Space.Compact>
|
)}
|
||||||
)
|
|
||||||
}))}
|
|
||||||
suffixIcon={<ChevronDown strokeWidth={1.5} size={16} color="var(--color-text-3)" />}
|
suffixIcon={<ChevronDown strokeWidth={1.5} size={16} color="var(--color-text-3)" />}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -436,23 +437,23 @@ const TranslatePage: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
showSearch
|
||||||
style={{ width: 160 }}
|
style={{ width: 160 }}
|
||||||
value={targetLanguage}
|
value={targetLanguage}
|
||||||
|
optionFilterProp="label"
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setTargetLanguage(value)
|
setTargetLanguage(value)
|
||||||
db.settings.put({ id: 'translate:target:language', value })
|
db.settings.put({ id: 'translate:target:language', value })
|
||||||
}}
|
}}
|
||||||
options={translateLanguageOptions().map((lang) => ({
|
options={TranslateLanguageOptions}
|
||||||
value: lang.value,
|
optionRender={(option) => (
|
||||||
label: (
|
<Space>
|
||||||
<Space.Compact direction="horizontal" block>
|
<span role="img" aria-label={(option.data as any).emoji}>
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
{(option.data as any).emoji}
|
||||||
{lang.emoji}
|
</span>
|
||||||
</span>
|
{option.label}
|
||||||
<Space.Compact block>{lang.label}</Space.Compact>
|
</Space>
|
||||||
</Space.Compact>
|
)}
|
||||||
)
|
|
||||||
}))}
|
|
||||||
suffixIcon={<ChevronDown strokeWidth={1.5} size={16} color="var(--color-text-3)" />}
|
suffixIcon={<ChevronDown strokeWidth={1.5} size={16} color="var(--color-text-3)" />}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -538,18 +539,25 @@ const TranslatePage: FC = () => {
|
|||||||
? `${t('translate.detected.language')}(${t(`languages.${detectedLanguage.toLowerCase()}`)})`
|
? `${t('translate.detected.language')}(${t(`languages.${detectedLanguage.toLowerCase()}`)})`
|
||||||
: t('translate.detected.language')
|
: t('translate.detected.language')
|
||||||
},
|
},
|
||||||
...translateLanguageOptions().map((lang) => ({
|
...TranslateLanguageOptions.map((lang) => ({
|
||||||
value: lang.value,
|
value: lang.value,
|
||||||
label: (
|
label: lang.label,
|
||||||
<Space.Compact direction="horizontal" block>
|
emoji: lang.emoji
|
||||||
<span role="img" aria-label={lang.emoji} style={{ marginRight: 8 }}>
|
|
||||||
{lang.emoji}
|
|
||||||
</span>
|
|
||||||
<Space.Compact block>{lang.label}</Space.Compact>
|
|
||||||
</Space.Compact>
|
|
||||||
)
|
|
||||||
}))
|
}))
|
||||||
]}
|
]}
|
||||||
|
optionRender={(option) => {
|
||||||
|
if (option.value === 'auto') {
|
||||||
|
return option.label
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<span role="img" aria-label={(option.data as any).emoji}>
|
||||||
|
{(option.data as any).emoji}
|
||||||
|
</span>
|
||||||
|
{option.label}
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}}
|
||||||
suffixIcon={<ChevronDown strokeWidth={1.5} size={16} color="var(--color-text-3)" />}
|
suffixIcon={<ChevronDown strokeWidth={1.5} size={16} color="var(--color-text-3)" />}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user