1+ import React , { useState , useMemo } from 'react' ;
2+ import { Box , Chip , Stack } from '@mui/material' ;
3+ import { useTheme , alpha as addOpacityToColor } from '@mui/material' ;
4+ import {
5+ isVisionModel ,
6+ isWebSearchModel ,
7+ isReasoningModel ,
8+ isFunctionCallingModel ,
9+ isCodeModel ,
10+ isEmbeddingModel ,
11+ isRerankModel
12+ } from '../utils/model' ;
13+
14+ interface ModelTagFilterProps {
15+ models : Array < {
16+ model : string ;
17+ provider : string ;
18+ [ key : string ] : any ;
19+ } > ;
20+ onFilteredModelsChange : ( filteredModels : any [ ] ) => void ;
21+ selectedTags ?: string [ ] ;
22+ onSelectedTagsChange ?: ( tags : string [ ] ) => void ;
23+ }
24+
25+ const ModelTagFilter : React . FC < ModelTagFilterProps > = ( {
26+ models,
27+ onFilteredModelsChange,
28+ selectedTags = [ ] ,
29+ onSelectedTagsChange
30+ } ) => {
31+ const theme = useTheme ( ) ;
32+ const [ internalSelectedTags , setInternalSelectedTags ] = useState < string [ ] > ( [ ] ) ;
33+
34+ // 使用外部传入的selectedTags或内部状态
35+ const currentSelectedTags = selectedTags . length > 0 ? selectedTags : internalSelectedTags ;
36+
37+ // 定义可用的标签类型
38+ const availableTags = [
39+ { key : 'all' , label : '全部' , color : 'default' as const } ,
40+ { key : 'reasoning' , label : '深度思考' , color : 'primary' as const } ,
41+ { key : 'vision' , label : '视觉' , color : 'secondary' as const } ,
42+ { key : 'function_calling' , label : '工具调用' , color : 'success' as const } ,
43+ { key : 'code' , label : '代码生成' , color : 'warning' as const } ,
44+ { key : 'embedding' , label : '向量' , color : 'error' as const } ,
45+ { key : 'rerank' , label : '重排' , color : 'default' as const }
46+ ] ;
47+
48+ // 根据模型计算每个标签的可用性
49+ const tagStats = useMemo ( ( ) => {
50+ const stats : Record < string , { available : boolean } > = { } ;
51+
52+ availableTags . forEach ( tag => {
53+ if ( tag . key === 'all' ) {
54+ stats [ tag . key ] = { available : true } ;
55+ return ;
56+ }
57+
58+ const count = models . filter ( model => {
59+ switch ( tag . key ) {
60+ case 'reasoning' :
61+ return isReasoningModel ( model . model , model . provider ) ;
62+ case 'vision' :
63+ return isVisionModel ( model . model , model . provider ) ;
64+ case 'websearch' :
65+ return isWebSearchModel ( model . model , model . provider ) ;
66+ case 'function_calling' :
67+ return isFunctionCallingModel ( model . model , model . provider ) ;
68+ case 'code' :
69+ return isCodeModel ( model . model , model . provider ) ;
70+ case 'embedding' :
71+ return isEmbeddingModel ( model . model , model . provider ) ;
72+ case 'rerank' :
73+ return isRerankModel ( model . model ) ;
74+ default :
75+ return false ;
76+ }
77+ } ) . length ;
78+
79+ stats [ tag . key ] = { available : count > 0 } ;
80+ } ) ;
81+
82+ return stats ;
83+ } , [ models ] ) ;
84+
85+ // 根据选中的标签筛选模型
86+ const filteredModels = useMemo ( ( ) => {
87+ if ( currentSelectedTags . length === 0 || currentSelectedTags . includes ( 'all' ) ) {
88+ return models ;
89+ }
90+
91+ return models . filter ( model => {
92+ return currentSelectedTags . some ( tag => {
93+ switch ( tag ) {
94+ case 'reasoning' :
95+ return isReasoningModel ( model . model , model . provider ) ;
96+ case 'vision' :
97+ return isVisionModel ( model . model , model . provider ) ;
98+ case 'websearch' :
99+ return isWebSearchModel ( model . model , model . provider ) ;
100+ case 'function_calling' :
101+ return isFunctionCallingModel ( model . model , model . provider ) ;
102+ case 'code' :
103+ return isCodeModel ( model . model , model . provider ) ;
104+ case 'embedding' :
105+ return isEmbeddingModel ( model . model , model . provider ) ;
106+ case 'rerank' :
107+ return isRerankModel ( model . model ) ;
108+ default :
109+ return false ;
110+ }
111+ } ) ;
112+ } ) ;
113+ } , [ models , currentSelectedTags ] ) ;
114+
115+ // 当筛选结果变化时通知父组件
116+ React . useEffect ( ( ) => {
117+ onFilteredModelsChange ( filteredModels ) ;
118+ } , [ filteredModels , onFilteredModelsChange ] ) ;
119+
120+ const handleTagClick = ( tagKey : string ) => {
121+ let newSelectedTags : string [ ] ;
122+
123+ if ( tagKey === 'all' ) {
124+ newSelectedTags = [ ] ;
125+ } else {
126+ // 单选逻辑:如果点击的是已选中的tag,则取消选择;否则只选择这个tag
127+ if ( currentSelectedTags . includes ( tagKey ) ) {
128+ newSelectedTags = [ ] ;
129+ } else {
130+ newSelectedTags = [ tagKey ] ;
131+ }
132+ }
133+
134+ if ( onSelectedTagsChange ) {
135+ onSelectedTagsChange ( newSelectedTags ) ;
136+ } else {
137+ setInternalSelectedTags ( newSelectedTags ) ;
138+ }
139+ } ;
140+
141+ const isTagSelected = ( tagKey : string ) => {
142+ if ( tagKey === 'all' ) {
143+ return currentSelectedTags . length === 0 || currentSelectedTags . includes ( 'all' ) ;
144+ }
145+ return currentSelectedTags . includes ( tagKey ) ;
146+ } ;
147+
148+ return (
149+ < Box >
150+ < Stack direction = "row" spacing = { 1 } flexWrap = "wrap" useFlexGap >
151+ { availableTags . map ( tag => {
152+ const stats = tagStats [ tag . key ] ;
153+ const selected = isTagSelected ( tag . key ) ;
154+
155+ return (
156+ < Chip
157+ key = { tag . key }
158+ label = { tag . label }
159+ variant = { selected ? 'filled' : 'outlined' }
160+ color = { selected ? tag . color : 'default' }
161+ size = "small"
162+ disabled = { ! stats . available }
163+ onClick = { ( e ) => {
164+ e . stopPropagation ( ) ;
165+ handleTagClick ( tag . key ) ;
166+ } }
167+ sx = { {
168+ mb : 0.5 ,
169+ cursor : stats . available ? 'pointer' : 'not-allowed' ,
170+ '&:hover' : stats . available ? {
171+ backgroundColor : selected
172+ ? addOpacityToColor ( theme . palette [ tag . color === 'default' ? 'primary' : tag . color ] . main , 0.8 )
173+ : addOpacityToColor ( theme . palette [ tag . color === 'default' ? 'primary' : tag . color ] . main , 0.1 )
174+ } : { } ,
175+ ...( selected && {
176+ backgroundColor : theme . palette [ tag . color === 'default' ? 'primary' : tag . color ] . main ,
177+ color : theme . palette [ tag . color === 'default' ? 'primary' : tag . color ] . contrastText
178+ } )
179+ } }
180+ />
181+ ) ;
182+ } ) }
183+ </ Stack >
184+ </ Box >
185+ ) ;
186+ } ;
187+
188+ export default ModelTagFilter ;
0 commit comments