mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
feat(provider): add agent support filter for provider list
- Add filter dropdown in provider search to filter by agent support - Create AnthropicProviderListPopover component for showing supported providers - Add filter=agent URL parameter support in ProviderList - Update AgentModal and CodeToolsPage to use the new popover component - Add getAnthropicSupportedProviders utility function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d41229c69b
commit
ddfbce071b
148
src/renderer/src/components/AnthropicProviderListPopover.tsx
Normal file
148
src/renderer/src/components/AnthropicProviderListPopover.tsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { ProviderAvatar } from '@renderer/components/ProviderAvatar'
|
||||||
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
|
import ImageStorage from '@renderer/services/ImageStorage'
|
||||||
|
import type { Provider } from '@renderer/types'
|
||||||
|
import { getFancyProviderName } from '@renderer/utils'
|
||||||
|
import { getClaudeSupportedProviders } from '@renderer/utils/provider'
|
||||||
|
import type { PopoverProps } from 'antd'
|
||||||
|
import { Popover } from 'antd'
|
||||||
|
import { ArrowUpRight, HelpCircle } from 'lucide-react'
|
||||||
|
import type { FC, ReactNode } from 'react'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
interface AnthropicProviderListPopoverProps {
|
||||||
|
/** Callback when provider is clicked */
|
||||||
|
onProviderClick?: () => void
|
||||||
|
/** Use window.navigate instead of Link (for non-router context like TopView) */
|
||||||
|
useWindowNavigate?: boolean
|
||||||
|
/** Custom trigger element, defaults to HelpCircle icon */
|
||||||
|
children?: ReactNode
|
||||||
|
/** Popover placement */
|
||||||
|
placement?: PopoverProps['placement']
|
||||||
|
/** Custom filter function for providers, defaults to getClaudeSupportedProviders */
|
||||||
|
filterProviders?: (providers: Provider[]) => Provider[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnthropicProviderListPopover: FC<AnthropicProviderListPopoverProps> = ({
|
||||||
|
onProviderClick,
|
||||||
|
useWindowNavigate = false,
|
||||||
|
children,
|
||||||
|
placement = 'right',
|
||||||
|
filterProviders = getClaudeSupportedProviders
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const allProviders = useAllProviders()
|
||||||
|
const providers = filterProviders(allProviders)
|
||||||
|
const [providerLogos, setProviderLogos] = useState<Record<string, string>>({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadAllLogos = async () => {
|
||||||
|
const logos: Record<string, string> = {}
|
||||||
|
for (const provider of providers) {
|
||||||
|
if (provider.id) {
|
||||||
|
try {
|
||||||
|
const logoData = await ImageStorage.get(`provider-${provider.id}`)
|
||||||
|
if (logoData) {
|
||||||
|
logos[provider.id] = logoData
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore errors loading logos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setProviderLogos(logos)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAllLogos()
|
||||||
|
}, [providers])
|
||||||
|
|
||||||
|
const handleClick = (providerId: string) => {
|
||||||
|
onProviderClick?.()
|
||||||
|
if (useWindowNavigate) {
|
||||||
|
window.navigate(`/settings/provider?id=${providerId}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<PopoverContent>
|
||||||
|
<PopoverTitle>{t('code.supported_providers')}</PopoverTitle>
|
||||||
|
<ProviderListContainer>
|
||||||
|
{providers.map((provider) =>
|
||||||
|
useWindowNavigate ? (
|
||||||
|
<ProviderItem key={provider.id} onClick={() => handleClick(provider.id)}>
|
||||||
|
<ProviderAvatar
|
||||||
|
provider={provider}
|
||||||
|
customLogos={providerLogos}
|
||||||
|
size={20}
|
||||||
|
style={{ width: 20, height: 20 }}
|
||||||
|
/>
|
||||||
|
{getFancyProviderName(provider)}
|
||||||
|
<ArrowUpRight size={14} />
|
||||||
|
</ProviderItem>
|
||||||
|
) : (
|
||||||
|
<ProviderLink
|
||||||
|
key={provider.id}
|
||||||
|
href={`/settings/provider?id=${provider.id}`}
|
||||||
|
onClick={() => handleClick(provider.id)}>
|
||||||
|
<ProviderAvatar
|
||||||
|
provider={provider}
|
||||||
|
customLogos={providerLogos}
|
||||||
|
size={20}
|
||||||
|
style={{ width: 20, height: 20 }}
|
||||||
|
/>
|
||||||
|
{getFancyProviderName(provider)}
|
||||||
|
<ArrowUpRight size={14} />
|
||||||
|
</ProviderLink>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ProviderListContainer>
|
||||||
|
</PopoverContent>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover content={content} trigger="hover" placement={placement}>
|
||||||
|
{children || <HelpCircle size={14} style={{ color: 'var(--color-text-3)', cursor: 'pointer' }} />}
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopoverContent = styled.div`
|
||||||
|
width: 200px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const PopoverTitle = styled.div`
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderListContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderItem = styled.div`
|
||||||
|
color: var(--color-text);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-link);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ProviderLink = styled.a`
|
||||||
|
color: var(--color-text);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-link);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default AnthropicProviderListPopover
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import AnthropicProviderListPopover from '@renderer/components/AnthropicProviderListPopover'
|
||||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||||
import { HelpTooltip } from '@renderer/components/TooltipIcons'
|
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { permissionModeCards } from '@renderer/config/agent'
|
import { permissionModeCards } from '@renderer/config/agent'
|
||||||
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
||||||
@ -16,6 +16,7 @@ import type {
|
|||||||
UpdateAgentForm
|
UpdateAgentForm
|
||||||
} from '@renderer/types'
|
} from '@renderer/types'
|
||||||
import { AgentConfigurationSchema, isAgentType } from '@renderer/types'
|
import { AgentConfigurationSchema, isAgentType } from '@renderer/types'
|
||||||
|
import { getAnthropicSupportedProviders } from '@renderer/utils/provider'
|
||||||
import { Alert, Button, Input, Modal, Select } from 'antd'
|
import { Alert, Button, Input, Modal, Select } from 'antd'
|
||||||
import { AlertTriangleIcon } from 'lucide-react'
|
import { AlertTriangleIcon } from 'lucide-react'
|
||||||
import type { ChangeEvent, FormEvent } from 'react'
|
import type { ChangeEvent, FormEvent } from 'react'
|
||||||
@ -420,7 +421,14 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
|
|||||||
<Label>
|
<Label>
|
||||||
{t('common.model')} <RequiredMark>*</RequiredMark>
|
{t('common.model')} <RequiredMark>*</RequiredMark>
|
||||||
</Label>
|
</Label>
|
||||||
<HelpTooltip title={t('agent.add.model.tooltip')} />
|
<AnthropicProviderListPopover
|
||||||
|
useWindowNavigate
|
||||||
|
filterProviders={getAnthropicSupportedProviders}
|
||||||
|
onProviderClick={() => {
|
||||||
|
setOpen(false)
|
||||||
|
resolve(undefined)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SelectAgentBaseModelButton
|
<SelectAgentBaseModelButton
|
||||||
agentBase={tempAgentBase}
|
agentBase={tempAgentBase}
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "Invalid Agent"
|
"invalid_agent": "Invalid Agent"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "Currently, only models that support Anthropic endpoints are available for the Agent feature."
|
"supported_providers": "Supported Providers",
|
||||||
|
"tooltip": "Currently, only models that support Anthropic endpoints are available for the Agent feature.",
|
||||||
|
"view_providers": "View supported providers"
|
||||||
},
|
},
|
||||||
"title": "Add Agent",
|
"title": "Add Agent",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "Check",
|
"docs_check": "Check",
|
||||||
"docs_more_details": "for more details",
|
"docs_more_details": "for more details",
|
||||||
|
"filter": {
|
||||||
|
"agent": "Agent Supported",
|
||||||
|
"all": "All Providers"
|
||||||
|
},
|
||||||
|
"filter_agent": "Filter Agent Supported Providers",
|
||||||
"get_api_key": "Get API Key",
|
"get_api_key": "Get API Key",
|
||||||
"misc": "Other",
|
"misc": "Other",
|
||||||
"no_models_for_check": "No models available for checking (e.g. chat models)",
|
"no_models_for_check": "No models available for checking (e.g. chat models)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "无效的 Agent"
|
"invalid_agent": "无效的 Agent"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "目前,只有支持 Anthropic 端点的模型可用于 Agent 功能。"
|
"supported_providers": "支持的服务商",
|
||||||
|
"tooltip": "目前,只有支持 Anthropic 端点的模型可用于 Agent 功能。",
|
||||||
|
"view_providers": "查看支持的服务商"
|
||||||
},
|
},
|
||||||
"title": "添加 Agent",
|
"title": "添加 Agent",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "查看",
|
"docs_check": "查看",
|
||||||
"docs_more_details": "获取更多详情",
|
"docs_more_details": "获取更多详情",
|
||||||
|
"filter": {
|
||||||
|
"agent": "支持 Agent",
|
||||||
|
"all": "全部服务商"
|
||||||
|
},
|
||||||
|
"filter_agent": "筛选支持 Agent 的服务商",
|
||||||
"get_api_key": "点击这里获取密钥",
|
"get_api_key": "点击这里获取密钥",
|
||||||
"misc": "其他",
|
"misc": "其他",
|
||||||
"no_models_for_check": "没有可以被检测的模型(例如对话模型)",
|
"no_models_for_check": "没有可以被检测的模型(例如对话模型)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "無效的 Agent"
|
"invalid_agent": "無效的 Agent"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "目前,僅支援 Anthropic 端點的模型可供代理功能使用。"
|
"supported_providers": "支援的服務商",
|
||||||
|
"tooltip": "目前,僅支援 Anthropic 端點的模型可供代理功能使用。",
|
||||||
|
"view_providers": "檢視支援的服務商"
|
||||||
},
|
},
|
||||||
"title": "新增 Agent",
|
"title": "新增 Agent",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "檢查",
|
"docs_check": "檢查",
|
||||||
"docs_more_details": "檢視更多細節",
|
"docs_more_details": "檢視更多細節",
|
||||||
|
"filter": {
|
||||||
|
"agent": "支援 Agent",
|
||||||
|
"all": "全部服務商"
|
||||||
|
},
|
||||||
|
"filter_agent": "篩選支援 Agent 的服務商",
|
||||||
"get_api_key": "點選這裡取得金鑰",
|
"get_api_key": "點選這裡取得金鑰",
|
||||||
"misc": "其他",
|
"misc": "其他",
|
||||||
"no_models_for_check": "沒有可以被檢查的模型(例如對話模型)",
|
"no_models_for_check": "沒有可以被檢查的模型(例如對話模型)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "Ungültiger Agent"
|
"invalid_agent": "Ungültiger Agent"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "Derzeit sind für die Agent-Funktion nur Modelle verfügbar, die Anthropic-Endpunkte unterstützen."
|
"supported_providers": "Unterstützte Anbieter",
|
||||||
|
"tooltip": "Derzeit sind für die Agent-Funktion nur Modelle verfügbar, die Anthropic-Endpunkte unterstützen.",
|
||||||
|
"view_providers": "Unterstützte Anbieter anzeigen"
|
||||||
},
|
},
|
||||||
"title": "Agent hinzufügen",
|
"title": "Agent hinzufügen",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "Anzeigen",
|
"docs_check": "Anzeigen",
|
||||||
"docs_more_details": "Weitere Details anzeigen",
|
"docs_more_details": "Weitere Details anzeigen",
|
||||||
|
"filter": {
|
||||||
|
"agent": "Vom Agenten unterstützt",
|
||||||
|
"all": "Alle Anbieter"
|
||||||
|
},
|
||||||
|
"filter_agent": "Von Filter-Agent unterstützte Anbieter",
|
||||||
"get_api_key": "Hier klicken um Schlüssel zu erhalten",
|
"get_api_key": "Hier klicken um Schlüssel zu erhalten",
|
||||||
"misc": "Sonstige",
|
"misc": "Sonstige",
|
||||||
"no_models_for_check": "Keine testbaren Modelle (z.B. Chat-Modelle)",
|
"no_models_for_check": "Keine testbaren Modelle (z.B. Chat-Modelle)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "Μη έγκυρος Agent"
|
"invalid_agent": "Μη έγκυρος Agent"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "Προς το παρόν, μόνο μοντέλα που υποστηρίζουν τελικά σημεία Anthropic είναι διαθέσιμα για τη λειτουργία Agent."
|
"supported_providers": "Υποστηριζόμενοι Πάροχοι",
|
||||||
|
"tooltip": "Προς το παρόν, μόνο μοντέλα που υποστηρίζουν τελικά σημεία Anthropic είναι διαθέσιμα για τη λειτουργία Agent.",
|
||||||
|
"view_providers": "Δείτε τους υποστηριζόμενους παρόχους"
|
||||||
},
|
},
|
||||||
"title": "Προσθήκη Agent",
|
"title": "Προσθήκη Agent",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "Άνοιγμα",
|
"docs_check": "Άνοιγμα",
|
||||||
"docs_more_details": "Λάβετε περισσότερες λεπτομέρειες",
|
"docs_more_details": "Λάβετε περισσότερες λεπτομέρειες",
|
||||||
|
"filter": {
|
||||||
|
"agent": "Υποστηριζόμενος από πράκτορα",
|
||||||
|
"all": "Όλοι οι Πάροχοι"
|
||||||
|
},
|
||||||
|
"filter_agent": "Υποστηριζόμενοι Πάροχοι του Φίλτρου Πράκτορα",
|
||||||
"get_api_key": "Κάντε κλικ εδώ για να πάρετε κλειδί",
|
"get_api_key": "Κάντε κλικ εδώ για να πάρετε κλειδί",
|
||||||
"misc": "Άλλο",
|
"misc": "Άλλο",
|
||||||
"no_models_for_check": "Δεν υπάρχουν μοντέλα για έλεγχο (π.χ. μοντέλα συνομιλίας)",
|
"no_models_for_check": "Δεν υπάρχουν μοντέλα για έλεγχο (π.χ. μοντέλα συνομιλίας)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "Agent inválido"
|
"invalid_agent": "Agent inválido"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "Actualmente, solo los modelos que admiten puntos finales de Anthropic están disponibles para la función Agente."
|
"supported_providers": "Proveedores Admitidos",
|
||||||
|
"tooltip": "Actualmente, solo los modelos que admiten puntos finales de Anthropic están disponibles para la función Agente.",
|
||||||
|
"view_providers": "Ver proveedores compatibles"
|
||||||
},
|
},
|
||||||
"title": "Agregar Agente",
|
"title": "Agregar Agente",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "Ver",
|
"docs_check": "Ver",
|
||||||
"docs_more_details": "Obtener más detalles",
|
"docs_more_details": "Obtener más detalles",
|
||||||
|
"filter": {
|
||||||
|
"agent": "Soporte de Agente",
|
||||||
|
"all": "Todos los Proveedores"
|
||||||
|
},
|
||||||
|
"filter_agent": "Proveedores compatibles con el agente de filtrado",
|
||||||
"get_api_key": "Haga clic aquí para obtener la clave",
|
"get_api_key": "Haga clic aquí para obtener la clave",
|
||||||
"misc": "otro",
|
"misc": "otro",
|
||||||
"no_models_for_check": "No hay modelos disponibles para revisar (por ejemplo, modelos de conversación)",
|
"no_models_for_check": "No hay modelos disponibles para revisar (por ejemplo, modelos de conversación)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "Agent invalide"
|
"invalid_agent": "Agent invalide"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "Actuellement, seuls les modèles qui prennent en charge les points de terminaison Anthropic sont disponibles pour la fonctionnalité Agent."
|
"supported_providers": "Fournisseurs pris en charge",
|
||||||
|
"tooltip": "Actuellement, seuls les modèles qui prennent en charge les points de terminaison Anthropic sont disponibles pour la fonctionnalité Agent.",
|
||||||
|
"view_providers": "Afficher les fournisseurs pris en charge"
|
||||||
},
|
},
|
||||||
"title": "Ajouter un agent",
|
"title": "Ajouter un agent",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "Voir",
|
"docs_check": "Voir",
|
||||||
"docs_more_details": "Obtenir plus de détails",
|
"docs_more_details": "Obtenir plus de détails",
|
||||||
|
"filter": {
|
||||||
|
"agent": "Prise en charge par un agent",
|
||||||
|
"all": "Tous les fournisseurs"
|
||||||
|
},
|
||||||
|
"filter_agent": "Fournisseurs pris en charge par l'agent de filtrage",
|
||||||
"get_api_key": "Cliquez ici pour obtenir une clé",
|
"get_api_key": "Cliquez ici pour obtenir une clé",
|
||||||
"misc": "autre",
|
"misc": "autre",
|
||||||
"no_models_for_check": "Aucun modèle détectable (par exemple, modèle de chat)",
|
"no_models_for_check": "Aucun modèle détectable (par exemple, modèle de chat)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "無効なエージェント"
|
"invalid_agent": "無効なエージェント"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "現在、エージェント機能では、Anthropicエンドポイントをサポートするモデルのみが利用可能です。"
|
"supported_providers": "サポートされているプロバイダー",
|
||||||
|
"tooltip": "現在、エージェント機能では、Anthropicエンドポイントをサポートするモデルのみが利用可能です。",
|
||||||
|
"view_providers": "サポートされているプロバイダーを表示"
|
||||||
},
|
},
|
||||||
"title": "エージェントを追加",
|
"title": "エージェントを追加",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "チェック",
|
"docs_check": "チェック",
|
||||||
"docs_more_details": "詳細を確認",
|
"docs_more_details": "詳細を確認",
|
||||||
|
"filter": {
|
||||||
|
"agent": "エージェントサポート",
|
||||||
|
"all": "すべてのプロバイダー"
|
||||||
|
},
|
||||||
|
"filter_agent": "フィルターエージェント対応プロバイダー",
|
||||||
"get_api_key": "APIキーを取得",
|
"get_api_key": "APIキーを取得",
|
||||||
"misc": "その他",
|
"misc": "その他",
|
||||||
"no_models_for_check": "チェックするモデルがありません(例:会話モデル)",
|
"no_models_for_check": "チェックするモデルがありません(例:会話モデル)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "Agent inválido"
|
"invalid_agent": "Agent inválido"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "Atualmente, apenas modelos que suportam endpoints da Anthropic estão disponíveis para o recurso Agente."
|
"supported_providers": "Provedores Suportados",
|
||||||
|
"tooltip": "Atualmente, apenas modelos que suportam endpoints da Anthropic estão disponíveis para o recurso Agente.",
|
||||||
|
"view_providers": "Ver provedores suportados"
|
||||||
},
|
},
|
||||||
"title": "Adicionar Agente",
|
"title": "Adicionar Agente",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "Verificar",
|
"docs_check": "Verificar",
|
||||||
"docs_more_details": "Obter mais detalhes",
|
"docs_more_details": "Obter mais detalhes",
|
||||||
|
"filter": {
|
||||||
|
"agent": "Suportado por Agente",
|
||||||
|
"all": "Todos os Provedores"
|
||||||
|
},
|
||||||
|
"filter_agent": "Agente de Filtro - Provedores Suportados",
|
||||||
"get_api_key": "Clique aqui para obter a chave",
|
"get_api_key": "Clique aqui para obter a chave",
|
||||||
"misc": "outro",
|
"misc": "outro",
|
||||||
"no_models_for_check": "Não há modelos disponíveis para verificação (por exemplo, modelos de conversa)",
|
"no_models_for_check": "Não há modelos disponíveis para verificação (por exemplo, modelos de conversa)",
|
||||||
|
|||||||
@ -7,7 +7,9 @@
|
|||||||
"invalid_agent": "Недействительный агент"
|
"invalid_agent": "Недействительный агент"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"tooltip": "В настоящее время для функции агента доступны только модели, поддерживающие конечные точки Anthropic."
|
"supported_providers": "Поддерживаемые поставщики",
|
||||||
|
"tooltip": "В настоящее время для функции агента доступны только модели, поддерживающие конечные точки Anthropic.",
|
||||||
|
"view_providers": "Просмотреть поддерживаемых поставщиков"
|
||||||
},
|
},
|
||||||
"title": "Добавить агента",
|
"title": "Добавить агента",
|
||||||
"type": {
|
"type": {
|
||||||
@ -4508,6 +4510,11 @@
|
|||||||
},
|
},
|
||||||
"docs_check": "Проверить",
|
"docs_check": "Проверить",
|
||||||
"docs_more_details": "для получения дополнительной информации",
|
"docs_more_details": "для получения дополнительной информации",
|
||||||
|
"filter": {
|
||||||
|
"agent": "Поддержка агента",
|
||||||
|
"all": "Все поставщики"
|
||||||
|
},
|
||||||
|
"filter_agent": "Поддерживаемые поставщики агента фильтрации",
|
||||||
"get_api_key": "Получить ключ API",
|
"get_api_key": "Получить ключ API",
|
||||||
"misc": "другие",
|
"misc": "другие",
|
||||||
"no_models_for_check": "Нет моделей для проверки (например, диалоговые модели)",
|
"no_models_for_check": "Нет моделей для проверки (например, диалоговые модели)",
|
||||||
|
|||||||
@ -1,29 +1,26 @@
|
|||||||
import AiProvider from '@renderer/aiCore'
|
import AiProvider from '@renderer/aiCore'
|
||||||
|
import AnthropicProviderListPopover from '@renderer/components/AnthropicProviderListPopover'
|
||||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
import ModelSelector from '@renderer/components/ModelSelector'
|
import ModelSelector from '@renderer/components/ModelSelector'
|
||||||
import { isMac, isWin } from '@renderer/config/constant'
|
import { isMac, isWin } from '@renderer/config/constant'
|
||||||
import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models'
|
import { isEmbeddingModel, isRerankModel, isTextToImageModel } from '@renderer/config/models'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
|
||||||
import { useCodeTools } from '@renderer/hooks/useCodeTools'
|
import { useCodeTools } from '@renderer/hooks/useCodeTools'
|
||||||
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
|
import { useProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
|
||||||
import { getProviderByModel } from '@renderer/services/AssistantService'
|
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||||
import { loggerService } from '@renderer/services/LoggerService'
|
import { loggerService } from '@renderer/services/LoggerService'
|
||||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import { setIsBunInstalled } from '@renderer/store/mcp'
|
import { setIsBunInstalled } from '@renderer/store/mcp'
|
||||||
import type { EndpointType, Model } from '@renderer/types'
|
import type { EndpointType, Model } from '@renderer/types'
|
||||||
import { getClaudeSupportedProviders } from '@renderer/utils/provider'
|
|
||||||
import type { TerminalConfig } from '@shared/config/constant'
|
import type { TerminalConfig } from '@shared/config/constant'
|
||||||
import { codeTools, terminalApps } from '@shared/config/constant'
|
import { codeTools, terminalApps } from '@shared/config/constant'
|
||||||
import { isSiliconAnthropicCompatibleModel } from '@shared/config/providers'
|
import { isSiliconAnthropicCompatibleModel } from '@shared/config/providers'
|
||||||
import { Alert, Avatar, Button, Checkbox, Input, Popover, Select, Space, Tooltip } from 'antd'
|
import { Alert, Button, Checkbox, Input, Select, Space, Tooltip } from 'antd'
|
||||||
import { ArrowUpRight, Download, FolderOpen, HelpCircle, Terminal, X } from 'lucide-react'
|
import { Download, FolderOpen, Terminal, X } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link } from 'react-router-dom'
|
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -40,7 +37,6 @@ const logger = loggerService.withContext('CodeToolsPage')
|
|||||||
const CodeToolsPage: FC = () => {
|
const CodeToolsPage: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { providers } = useProviders()
|
const { providers } = useProviders()
|
||||||
const allProviders = useAllProviders()
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const isBunInstalled = useAppSelector((state) => state.mcp.isBunInstalled)
|
const isBunInstalled = useAppSelector((state) => state.mcp.isBunInstalled)
|
||||||
const {
|
const {
|
||||||
@ -372,48 +368,7 @@ const CodeToolsPage: FC = () => {
|
|||||||
<SettingsItem>
|
<SettingsItem>
|
||||||
<div className="settings-label">
|
<div className="settings-label">
|
||||||
{t('code.model')}
|
{t('code.model')}
|
||||||
{selectedCliTool === 'claude-code' && (
|
{selectedCliTool === 'claude-code' && <AnthropicProviderListPopover />}
|
||||||
<Popover
|
|
||||||
content={
|
|
||||||
<div style={{ width: 200 }}>
|
|
||||||
<div style={{ marginBottom: 8, fontWeight: 500 }}>{t('code.supported_providers')}</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 8
|
|
||||||
}}>
|
|
||||||
{getClaudeSupportedProviders(allProviders).map((provider) => {
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
key={provider.id}
|
|
||||||
style={{
|
|
||||||
color: 'var(--color-text)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 4
|
|
||||||
}}
|
|
||||||
to={`/settings/provider?id=${provider.id}`}>
|
|
||||||
<ProviderLogo shape="square" src={getProviderLogo(provider.id)} size={20} />
|
|
||||||
{getProviderLabel(provider.id)}
|
|
||||||
<ArrowUpRight size={14} />
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
trigger="hover"
|
|
||||||
placement="right">
|
|
||||||
<HelpCircle
|
|
||||||
size={14}
|
|
||||||
style={{
|
|
||||||
color: 'var(--color-text-3)',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
providers={availableProviders}
|
providers={availableProviders}
|
||||||
@ -621,8 +576,4 @@ const BunInstallAlert = styled.div`
|
|||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const ProviderLogo = styled(Avatar)`
|
|
||||||
border-radius: 4px;
|
|
||||||
`
|
|
||||||
|
|
||||||
export default CodeToolsPage
|
export default CodeToolsPage
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { isSystemProvider } from '@renderer/types'
|
|||||||
import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils'
|
import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils'
|
||||||
import type { MenuProps } from 'antd'
|
import type { MenuProps } from 'antd'
|
||||||
import { Button, Dropdown, Input, Tag } from 'antd'
|
import { Button, Dropdown, Input, Tag } from 'antd'
|
||||||
import { GripVertical, PlusIcon, Search, UserPen } from 'lucide-react'
|
import { Check, Filter, GripVertical, PlusIcon, Search, UserPen } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { startTransition, useCallback, useEffect, useRef, useState } from 'react'
|
import { startTransition, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -42,6 +42,7 @@ const ProviderList: FC = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [searchText, setSearchText] = useState<string>('')
|
const [searchText, setSearchText] = useState<string>('')
|
||||||
const [dragging, setDragging] = useState(false)
|
const [dragging, setDragging] = useState(false)
|
||||||
|
const [agentFilterEnabled, setAgentFilterEnabled] = useState(false)
|
||||||
const [providerLogos, setProviderLogos] = useState<Record<string, string>>({})
|
const [providerLogos, setProviderLogos] = useState<Record<string, string>>({})
|
||||||
const listRef = useRef<DraggableVirtualListRef>(null)
|
const listRef = useRef<DraggableVirtualListRef>(null)
|
||||||
|
|
||||||
@ -71,7 +72,16 @@ const ProviderList: FC = () => {
|
|||||||
}, [providers])
|
}, [providers])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchParams.get('id')) {
|
let shouldUpdate = false
|
||||||
|
const hasFilterParam = searchParams.get('filter') === 'agent'
|
||||||
|
|
||||||
|
// Handle filter param first - when filter is enabled, ignore id param
|
||||||
|
if (hasFilterParam) {
|
||||||
|
setAgentFilterEnabled(true)
|
||||||
|
searchParams.delete('filter')
|
||||||
|
searchParams.delete('id') // Clear id param when filter is enabled
|
||||||
|
shouldUpdate = true
|
||||||
|
} else if (searchParams.get('id')) {
|
||||||
const providerId = searchParams.get('id')
|
const providerId = searchParams.get('id')
|
||||||
const provider = providers.find((p) => p.id === providerId)
|
const provider = providers.find((p) => p.id === providerId)
|
||||||
if (provider) {
|
if (provider) {
|
||||||
@ -89,6 +99,10 @@ const ProviderList: FC = () => {
|
|||||||
setSelectedProvider(providers[0])
|
setSelectedProvider(providers[0])
|
||||||
}
|
}
|
||||||
searchParams.delete('id')
|
searchParams.delete('id')
|
||||||
|
shouldUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpdate) {
|
||||||
setSearchParams(searchParams)
|
setSearchParams(searchParams)
|
||||||
}
|
}
|
||||||
}, [providers, searchParams, setSearchParams, setSelectedProvider, setTimeoutTimer])
|
}, [providers, searchParams, setSearchParams, setSelectedProvider, setTimeoutTimer])
|
||||||
@ -282,6 +296,11 @@ const ProviderList: FC = () => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter by agent support
|
||||||
|
if (agentFilterEnabled && !provider.anthropicApiHost) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
||||||
const isProviderMatch = matchKeywordsInProvider(keywords, provider)
|
const isProviderMatch = matchKeywordsInProvider(keywords, provider)
|
||||||
const isModelMatch = provider.models.some((model) => matchKeywordsInModel(keywords, model))
|
const isModelMatch = provider.models.some((model) => matchKeywordsInModel(keywords, model))
|
||||||
@ -316,7 +335,34 @@ const ProviderList: FC = () => {
|
|||||||
placeholder={t('settings.provider.search')}
|
placeholder={t('settings.provider.search')}
|
||||||
value={searchText}
|
value={searchText}
|
||||||
style={{ borderRadius: 'var(--list-item-border-radius)', height: 35 }}
|
style={{ borderRadius: 'var(--list-item-border-radius)', height: 35 }}
|
||||||
suffix={<Search size={14} />}
|
prefix={<Search size={14} />}
|
||||||
|
suffix={
|
||||||
|
<Dropdown
|
||||||
|
menu={{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: t('settings.provider.filter.all'),
|
||||||
|
key: 'all',
|
||||||
|
icon: agentFilterEnabled ? <CheckPlaceholder /> : <Check size={14} />,
|
||||||
|
onClick: () => setAgentFilterEnabled(false)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.provider.filter.agent'),
|
||||||
|
key: 'agent',
|
||||||
|
icon: agentFilterEnabled ? <Check size={14} /> : <CheckPlaceholder />,
|
||||||
|
onClick: () => setAgentFilterEnabled(true)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
trigger={['click']}>
|
||||||
|
<FilterButton>
|
||||||
|
<Filter
|
||||||
|
size={14}
|
||||||
|
className={agentFilterEnabled ? 'text-[var(--color-primary)]' : 'text-[var(--color-text-3)]'}
|
||||||
|
/>
|
||||||
|
</FilterButton>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
@ -457,4 +503,20 @@ const AddButtonWrapper = styled.div`
|
|||||||
padding: 10px 8px;
|
padding: 10px 8px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const FilterButton = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
`
|
||||||
|
|
||||||
|
const CheckPlaceholder = styled.span`
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
`
|
||||||
|
|
||||||
export default ProviderList
|
export default ProviderList
|
||||||
|
|||||||
@ -2,9 +2,11 @@ import { type AzureOpenAIProvider, type Provider, SystemProviderIds } from '@ren
|
|||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
getAnthropicSupportedProviders,
|
||||||
getClaudeSupportedProviders,
|
getClaudeSupportedProviders,
|
||||||
isAIGatewayProvider,
|
isAIGatewayProvider,
|
||||||
isAnthropicProvider,
|
isAnthropicProvider,
|
||||||
|
isAnthropicSupportedProvider,
|
||||||
isAzureOpenAIProvider,
|
isAzureOpenAIProvider,
|
||||||
isCherryAIProvider,
|
isCherryAIProvider,
|
||||||
isGeminiProvider,
|
isGeminiProvider,
|
||||||
@ -67,6 +69,26 @@ describe('provider utils', () => {
|
|||||||
expect(getClaudeSupportedProviders(providers)).toEqual(providers.slice(0, 3))
|
expect(getClaudeSupportedProviders(providers)).toEqual(providers.slice(0, 3))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('filters Anthropic supported providers', () => {
|
||||||
|
const providers = [
|
||||||
|
createProvider({ id: 'anthropic-official', type: 'anthropic' }),
|
||||||
|
createProvider({ id: 'custom-host', anthropicApiHost: 'https://anthropic.local' }),
|
||||||
|
createProvider({ id: 'aihubmix' }),
|
||||||
|
createProvider({ id: 'other' })
|
||||||
|
]
|
||||||
|
|
||||||
|
expect(getAnthropicSupportedProviders(providers)).toEqual(providers.slice(0, 2))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks Anthropic supported provider', () => {
|
||||||
|
expect(isAnthropicSupportedProvider(createProvider({ id: 'anthropic-official', type: 'anthropic' }))).toBe(true)
|
||||||
|
expect(
|
||||||
|
isAnthropicSupportedProvider(createProvider({ id: 'custom-host', anthropicApiHost: 'https://anthropic.local' }))
|
||||||
|
).toBe(true)
|
||||||
|
expect(isAnthropicSupportedProvider(createProvider({ id: 'aihubmix' }))).toBe(false)
|
||||||
|
expect(isAnthropicSupportedProvider(createProvider({ id: 'other' }))).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
it('evaluates message array content support', () => {
|
it('evaluates message array content support', () => {
|
||||||
expect(isSupportArrayContentProvider(createProvider())).toBe(true)
|
expect(isSupportArrayContentProvider(createProvider())).toBe(true)
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,14 @@ export const getClaudeSupportedProviders = (providers: Provider[]) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAnthropicSupportedProviders = (providers: Provider[]) => {
|
||||||
|
return providers.filter(isAnthropicSupportedProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isAnthropicSupportedProvider = (provider: Provider) => {
|
||||||
|
return provider.type === 'anthropic' || !!provider.anthropicApiHost
|
||||||
|
}
|
||||||
|
|
||||||
const NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS = [
|
const NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS = [
|
||||||
'deepseek',
|
'deepseek',
|
||||||
'baichuan',
|
'baichuan',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user