搜索切换模型

This commit is contained in:
1600822305 2025-04-10 13:36:43 +08:00
parent a0be911dc9
commit dcdd8b4031

View File

@ -4,7 +4,7 @@ import { getModelLogo, isEmbeddingModel, isRerankModel } from '@renderer/config/
import db from '@renderer/databases' import db from '@renderer/databases'
import { useProviders } from '@renderer/hooks/useProvider' import { useProviders } from '@renderer/hooks/useProvider'
import { getModelUniqId } from '@renderer/services/ModelService' import { getModelUniqId } from '@renderer/services/ModelService'
import { Model } from '@renderer/types' import { Model, Provider } from '@renderer/types'
import { Avatar, Divider, Empty, Input, InputRef, Menu, MenuProps, Modal } from 'antd' import { Avatar, Divider, Empty, Input, InputRef, Menu, MenuProps, Modal } from 'antd'
import { first, sortBy } from 'lodash' import { first, sortBy } from 'lodash'
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
@ -35,6 +35,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
const scrollContainerRef = useRef<HTMLDivElement>(null) const scrollContainerRef = useRef<HTMLDivElement>(null)
const [keyboardSelectedId, setKeyboardSelectedId] = useState<string>('') const [keyboardSelectedId, setKeyboardSelectedId] = useState<string>('')
const menuItemRefs = useRef<Record<string, HTMLElement | null>>({}) const menuItemRefs = useRef<Record<string, HTMLElement | null>>({})
const [selectedProvider, setSelectedProvider] = useState<string>('all')
const setMenuItemRef = useCallback( const setMenuItemRef = useCallback(
(key: string) => (el: HTMLElement | null) => { (key: string) => (el: HTMLElement | null) => {
@ -360,13 +361,48 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
const selectedKeys = keyboardSelectedId ? [keyboardSelectedId] : model ? [getModelUniqId(model)] : [] const selectedKeys = keyboardSelectedId ? [keyboardSelectedId] : model ? [getModelUniqId(model)] : []
// 创建左侧提供商列表
const providerItems = [
{ key: 'all', label: t('models.all') || '全部' },
...(pinnedModels.length > 0 ? [{ key: 'pinned', label: t('models.pinned') || '已固定' }] : []),
...providers.map(p => ({
key: p.id,
label: p.isSystem ? t(`provider.${p.id}`) || p.name : p.name,
icon: <Avatar src={getModelLogo(p.id)} size={20}>{first(p.name)}</Avatar>
}))
];
// 根据选中的提供商筛选模型
const getFilteredModelsByProvider = useCallback(() => {
if (searchText.trim()) {
// 搜索模式下显示所有匹配的模型
return processedItems;
}
if (selectedProvider === 'all') {
// 显示所有模型
return processedItems;
} else if (selectedProvider === 'pinned') {
// 只显示固定的模型
const pinnedGroup = processedItems.find(item => item && 'key' in item && item.key === 'pinned');
return pinnedGroup ? [pinnedGroup] : [];
} else {
// 显示选中提供商的模型
const providerGroup = processedItems.find(item => item && 'key' in item && item.key === selectedProvider);
return providerGroup ? [providerGroup] : [];
}
}, [selectedProvider, processedItems, searchText]);
// 获取要显示的模型列表
const displayedItems = getFilteredModelsByProvider();
return ( return (
<Modal <Modal
centered centered
open={open} open={open}
onCancel={onCancel} onCancel={onCancel}
afterClose={onClose} afterClose={onClose}
width={600} width={800}
transitionName="ant-move-down" transitionName="ant-move-down"
styles={{ styles={{
content: { content: {
@ -404,25 +440,38 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
/> />
</HStack> </HStack>
<Divider style={{ margin: 0, borderBlockStartWidth: 0.5 }} /> <Divider style={{ margin: 0, borderBlockStartWidth: 0.5 }} />
<Scrollbar style={{ height: '50vh' }} ref={scrollContainerRef}> <SplitContainer>
<Container> <LeftPanel>
{processedItems.length > 0 ? ( <Scrollbar style={{ height: '50vh' }}>
<StyledMenu <ProviderMenu
items={processedItems} items={providerItems}
selectedKeys={selectedKeys} selectedKeys={[selectedProvider]}
mode="inline" onClick={({ key }) => setSelectedProvider(key as string)}
inlineIndent={6}
onSelect={({ key }) => {
setKeyboardSelectedId(key as string)
}}
/> />
) : ( </Scrollbar>
<EmptyState> </LeftPanel>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> <RightPanel>
</EmptyState> <Scrollbar style={{ height: '50vh' }} ref={scrollContainerRef}>
)} <Container>
</Container> {displayedItems.length > 0 ? (
</Scrollbar> <StyledMenu
items={displayedItems}
selectedKeys={selectedKeys}
mode="inline"
inlineIndent={6}
onSelect={({ key }) => {
setKeyboardSelectedId(key as string)
}}
/>
) : (
<EmptyState>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</EmptyState>
)}
</Container>
</Scrollbar>
</RightPanel>
</SplitContainer>
</Modal> </Modal>
) )
} }
@ -431,11 +480,44 @@ const Container = styled.div`
margin-top: 10px; margin-top: 10px;
` `
const SplitContainer = styled.div`
display: flex;
flex-direction: row;
height: 50vh;
`
const LeftPanel = styled.div`
width: 200px;
border-right: 1px solid var(--color-border);
overflow-y: auto;
`
const RightPanel = styled.div`
flex: 1;
overflow-y: auto;
`
const ProviderMenu = styled(Menu)`
background-color: transparent;
border-inline-end: none !important;
.ant-menu-item {
height: 40px;
line-height: 40px;
margin: 0;
border-radius: 0;
&.ant-menu-item-selected {
background-color: var(--color-background-mute) !important;
color: var(--color-text-primary) !important;
}
}
`
const StyledMenu = styled(Menu)` const StyledMenu = styled(Menu)`
background-color: transparent; background-color: transparent;
padding: 5px; padding: 5px;
margin-top: -10px; margin-top: -10px;
max-height: calc(60vh - 50px);
.ant-menu-item-group-title { .ant-menu-item-group-title {
position: sticky; position: sticky;