-
Notifications
You must be signed in to change notification settings - Fork 19
feat: 为 Provider 添加 SupportModels 模型过滤功能 #121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,5 @@ | ||||||||||||||||||||||||||||||||
| import { useState, useMemo } from 'react' | ||||||||||||||||||||||||||||||||
| import { Globe, ChevronLeft, Key, Check, Trash2, Plus, ArrowRight, Zap } from 'lucide-react' | ||||||||||||||||||||||||||||||||
| import { Globe, ChevronLeft, Key, Check, Trash2, Plus, ArrowRight, Zap, Filter } from 'lucide-react' | ||||||||||||||||||||||||||||||||
| import { useTranslation } from 'react-i18next' | ||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||
| Dialog, | ||||||||||||||||||||||||||||||||
|
|
@@ -167,6 +167,97 @@ function ProviderModelMappings({ provider }: { provider: Provider }) { | |||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Provider Supported Models Section | ||||||||||||||||||||||||||||||||
| function ProviderSupportModels({ | ||||||||||||||||||||||||||||||||
| supportModels, | ||||||||||||||||||||||||||||||||
| onChange, | ||||||||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||||||||
| supportModels: string[] | ||||||||||||||||||||||||||||||||
| onChange: (models: string[]) => void | ||||||||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||||||||
| const { t } = useTranslation() | ||||||||||||||||||||||||||||||||
| const [newModel, setNewModel] = useState('') | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const handleAddModel = () => { | ||||||||||||||||||||||||||||||||
| if (!newModel.trim()) return | ||||||||||||||||||||||||||||||||
| const trimmedModel = newModel.trim() | ||||||||||||||||||||||||||||||||
| if (!supportModels.includes(trimmedModel)) { | ||||||||||||||||||||||||||||||||
| onChange([...supportModels, trimmedModel]) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| setNewModel('') | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const handleRemoveModel = (model: string) => { | ||||||||||||||||||||||||||||||||
| onChange(supportModels.filter(m => m !== model)) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-2 mb-4 border-b border-border pb-2"> | ||||||||||||||||||||||||||||||||
| <Filter size={18} className="text-blue-500" /> | ||||||||||||||||||||||||||||||||
| <h4 className="text-lg font-semibold text-foreground"> | ||||||||||||||||||||||||||||||||
| {t('providers.supportModels.title', 'Supported Models')} | ||||||||||||||||||||||||||||||||
| </h4> | ||||||||||||||||||||||||||||||||
| <span className="text-sm text-muted-foreground"> | ||||||||||||||||||||||||||||||||
| ({supportModels.length}) | ||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <div className="bg-card border border-border rounded-xl p-4"> | ||||||||||||||||||||||||||||||||
| <p className="text-xs text-muted-foreground mb-4"> | ||||||||||||||||||||||||||||||||
| {t('providers.supportModels.desc', 'Configure which models this provider supports. If empty, all models are supported. Supports wildcards like claude-* or gemini-*.')} | ||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {supportModels.length > 0 && ( | ||||||||||||||||||||||||||||||||
| <div className="flex flex-wrap gap-2 mb-4"> | ||||||||||||||||||||||||||||||||
| {supportModels.map((model) => ( | ||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||
| key={model} | ||||||||||||||||||||||||||||||||
| className="flex items-center gap-1 bg-muted/50 border border-border rounded-lg px-3 py-1.5" | ||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||
| <span className="text-sm">{model}</span> | ||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||
| onClick={() => handleRemoveModel(model)} | ||||||||||||||||||||||||||||||||
| className="text-muted-foreground hover:text-destructive ml-1" | ||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||
| <Trash2 className="h-3.5 w-3.5" /> | ||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||
|
Comment on lines
+219
to
+225
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 无障碍性:删除按钮缺少 aria-label 删除按钮没有提供屏幕阅读器可识别的标签,这会影响无障碍访问体验。 ♿ 建议的修复 <button
type="button"
onClick={() => handleRemoveModel(model)}
className="text-muted-foreground hover:text-destructive ml-1"
+ aria-label={t('common.remove', 'Remove') + ` ${model}`}
>
<Trash2 className="h-3.5 w-3.5" />
</button>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {supportModels.length === 0 && ( | ||||||||||||||||||||||||||||||||
| <div className="text-center py-6 mb-4"> | ||||||||||||||||||||||||||||||||
| <p className="text-muted-foreground text-sm"> | ||||||||||||||||||||||||||||||||
| {t('providers.supportModels.empty', 'No model filter configured. All models will be supported.')} | ||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-2 pt-4 border-t border-border"> | ||||||||||||||||||||||||||||||||
| <ModelInput | ||||||||||||||||||||||||||||||||
| value={newModel} | ||||||||||||||||||||||||||||||||
| onChange={setNewModel} | ||||||||||||||||||||||||||||||||
| placeholder={t('providers.supportModels.placeholder', 'e.g. claude-* or gemini-2.5-*')} | ||||||||||||||||||||||||||||||||
| className="flex-1 min-w-0 h-8 text-sm" | ||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||||||
| size="sm" | ||||||||||||||||||||||||||||||||
| onClick={handleAddModel} | ||||||||||||||||||||||||||||||||
| disabled={!newModel.trim()} | ||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||
| <Plus className="h-4 w-4 mr-1" /> | ||||||||||||||||||||||||||||||||
| {t('common.add')} | ||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| interface ProviderEditFlowProps { | ||||||||||||||||||||||||||||||||
| provider: Provider; | ||||||||||||||||||||||||||||||||
| onClose: () => void; | ||||||||||||||||||||||||||||||||
|
|
@@ -177,6 +268,7 @@ type EditFormData = { | |||||||||||||||||||||||||||||||
| baseURL: string; | ||||||||||||||||||||||||||||||||
| apiKey: string; | ||||||||||||||||||||||||||||||||
| clients: ClientConfig[]; | ||||||||||||||||||||||||||||||||
| supportModels: string[]; | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| export function ProviderEditFlow({ provider, onClose }: ProviderEditFlowProps) { | ||||||||||||||||||||||||||||||||
|
|
@@ -202,6 +294,7 @@ export function ProviderEditFlow({ provider, onClose }: ProviderEditFlowProps) { | |||||||||||||||||||||||||||||||
| baseURL: provider.config?.custom?.baseURL || '', | ||||||||||||||||||||||||||||||||
| apiKey: provider.config?.custom?.apiKey || '', | ||||||||||||||||||||||||||||||||
| clients: initClients(), | ||||||||||||||||||||||||||||||||
| supportModels: provider.supportModels || [], | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const updateClient = (clientId: ClientType, updates: Partial<ClientConfig>) => { | ||||||||||||||||||||||||||||||||
|
|
@@ -245,6 +338,7 @@ export function ProviderEditFlow({ provider, onClose }: ProviderEditFlowProps) { | |||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||
| supportedClientTypes, | ||||||||||||||||||||||||||||||||
| supportModels: formData.supportModels.length > 0 ? formData.supportModels : undefined, | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| await updateProvider.mutateAsync({ id: Number(provider.id), data }); | ||||||||||||||||||||||||||||||||
|
|
@@ -420,6 +514,12 @@ export function ProviderEditFlow({ provider, onClose }: ProviderEditFlowProps) { | |||||||||||||||||||||||||||||||
| <ClientsConfigSection clients={formData.clients} onUpdateClient={updateClient} /> | ||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {/* Provider Supported Models Filter */} | ||||||||||||||||||||||||||||||||
| <ProviderSupportModels | ||||||||||||||||||||||||||||||||
| supportModels={formData.supportModels} | ||||||||||||||||||||||||||||||||
| onChange={(models) => setFormData((prev) => ({ ...prev, supportModels: models }))} | ||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| {/* Provider Model Mappings */} | ||||||||||||||||||||||||||||||||
| <ProviderModelMappings provider={provider} /> | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
注释与实际实现可能不一致。
注释中提到"在 Route 匹配时会检查前置映射后的模型是否在支持列表中",但根据 PR 目标和
router.go中的实现,SupportModels检查使用的是原始 requestModel(映射前),模型映射是在 Executor 阶段执行的。建议修正注释以匹配实际行为:
📝 建议的注释修改
📝 Committable suggestion
🤖 Prompt for AI Agents