refactor: align create agent model selection with edit agent

This commit is contained in:
defi-failure 2025-11-06 12:06:09 +08:00
parent 84f2281506
commit 84bf94e2ff
2 changed files with 88 additions and 50 deletions

View File

@ -3,14 +3,14 @@ import ClaudeIcon from '@renderer/assets/images/models/claude.png'
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
import { TopView } from '@renderer/components/TopView'
import { permissionModeCards } from '@renderer/config/agent'
import { agentModelFilter, getModelLogoById } from '@renderer/config/models'
import { useAgents } from '@renderer/hooks/agents/useAgents'
import { useApiModels } from '@renderer/hooks/agents/useModels'
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton'
import type {
AddAgentForm,
AgentEntity,
AgentType,
ApiModel,
BaseAgentForm,
PermissionMode,
Tool,
@ -65,7 +65,6 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
const loadingRef = useRef(false)
const { addAgent } = useAgents()
const { updateAgent } = useUpdateAgent()
const { models } = useApiModels({ providerType: 'anthropic' })
const isEditing = (agent?: AgentWithTools) => agent !== undefined
const [form, setForm] = useState<BaseAgentForm>(() => buildAgentForm(agent))
@ -199,32 +198,26 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
}))
}, [])
const modelOptions = useMemo(() => {
return (models ?? [])
.filter((m) =>
agentModelFilter({
id: m.id,
provider: m.provider || '',
name: m.name,
group: ''
})
)
.map((model) => ({
value: model.id,
label: (
<OptionWrapper>
<Avatar src={getModelLogoById(model.id)} size={24} />
<span>{model.name}</span>
</OptionWrapper>
)
}))
}, [models])
// Create a temporary agentBase object for SelectAgentBaseModelButton
const tempAgentBase: AgentEntity = useMemo(
() => ({
id: agent?.id ?? 'temp-creating',
type: form.type,
name: form.name,
model: form.model,
accessible_paths: form.accessible_paths.length > 0 ? form.accessible_paths : ['/'],
allowed_tools: form.allowed_tools ?? [],
description: form.description,
instructions: form.instructions,
configuration: form.configuration,
created_at: agent?.created_at ?? new Date().toISOString(),
updated_at: agent?.updated_at ?? new Date().toISOString()
}),
[form, agent?.id, agent?.created_at, agent?.updated_at]
)
const onModelChange = useCallback((value: string) => {
setForm((prev) => ({
...prev,
model: value
}))
const handleModelSelect = useCallback(async (model: ApiModel) => {
setForm((prev) => ({ ...prev, model: model.id }))
}, [])
const onCancel = () => {
@ -336,7 +329,7 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
afterClose={onClose}
transitionName="animation-move-down"
centered
width={680}
width={500}
footer={null}>
<StyledForm onSubmit={onSubmit}>
<FormContent>
@ -363,17 +356,20 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
<Label>
{t('common.model')} <RequiredMark>*</RequiredMark>
</Label>
<Select
value={form.model || undefined}
onChange={onModelChange}
options={modelOptions}
placeholder={t('common.placeholders.select.model')}
style={{ width: '100%' }}
showSearch
filterOption={(input, option) => {
const label = option?.label as any
return label?.props?.children?.[1]?.toLowerCase().includes(input.toLowerCase()) || false
<SelectAgentBaseModelButton
agentBase={tempAgentBase}
onSelect={handleModelSelect}
fontSize={14}
avatarSize={24}
iconSize={16}
buttonStyle={{
padding: '8px 12px',
width: '100%',
border: '1px solid var(--color-border)',
borderRadius: 6,
height: 'auto'
}}
containerClassName="flex items-center justify-between w-full"
/>
</FormItem>

View File

@ -4,25 +4,56 @@ import { agentModelFilter } from '@renderer/config/models'
import { useApiModel } from '@renderer/hooks/agents/useModel'
import { getProviderNameById } from '@renderer/services/ProviderService'
import type { AgentBaseWithId, ApiModel } from '@renderer/types'
import { isAgentSessionEntity } from '@renderer/types'
import { isAgentEntity } from '@renderer/types'
import { getModelFilterByAgentType } from '@renderer/utils/agentSession'
import { apiModelAdapter } from '@renderer/utils/model'
import type { ButtonProps } from 'antd'
import { Button } from 'antd'
import { ChevronsUpDown } from 'lucide-react'
import type { FC } from 'react'
import type { CSSProperties, FC } from 'react'
import { useTranslation } from 'react-i18next'
interface Props {
agentBase: AgentBaseWithId
onSelect: (model: ApiModel) => Promise<void>
isDisabled?: boolean
/** Custom className for the button */
className?: string
/** Custom inline styles for the button (merged with default styles) */
buttonStyle?: CSSProperties
/** Custom button size */
buttonSize?: ButtonProps['size']
/** Custom avatar size */
avatarSize?: number
/** Custom font size */
fontSize?: number
/** Custom icon size */
iconSize?: number
/** Custom className for the inner container (e.g., for justify-between) */
containerClassName?: string
}
const SelectAgentBaseModelButton: FC<Props> = ({ agentBase: agent, onSelect, isDisabled }) => {
const SelectAgentBaseModelButton: FC<Props> = ({
agentBase: agent,
onSelect,
isDisabled,
className,
buttonStyle,
buttonSize = 'small',
avatarSize = 20,
fontSize = 12,
iconSize = 14,
containerClassName
}) => {
const { t } = useTranslation()
const model = useApiModel({ id: agent?.model })
const apiFilter = isAgentEntity(agent) ? getModelFilterByAgentType(agent.type) : undefined
const apiFilter = isAgentEntity(agent)
? getModelFilterByAgentType(agent.type)
: isAgentSessionEntity(agent)
? getModelFilterByAgentType(agent.agent_type)
: undefined
if (!agent) return null
@ -35,20 +66,31 @@ const SelectAgentBaseModelButton: FC<Props> = ({ agentBase: agent, onSelect, isD
const providerName = model?.provider ? getProviderNameById(model.provider) : model?.provider_name
// Merge default styles with custom styles
const mergedStyle: CSSProperties = {
borderRadius: 20,
fontSize,
padding: 2,
...buttonStyle
}
return (
<Button
size="small"
size={buttonSize}
type="text"
style={{ borderRadius: 20, fontSize: 12, padding: 2 }}
className={className}
style={mergedStyle}
onClick={onSelectModel}
disabled={isDisabled}>
<div className="flex items-center gap-1.5 overflow-x-hidden">
<ModelAvatar model={model ? apiModelAdapter(model) : undefined} size={20} />
<span className="truncate text-[var(--color-text)]">
{model ? model.name : t('button.select_model')} {providerName ? ' | ' + providerName : ''}
</span>
<div className={containerClassName || 'flex w-full items-center gap-1.5'}>
<div className="flex flex-1 items-center gap-1.5 overflow-x-hidden">
<ModelAvatar model={model ? apiModelAdapter(model) : undefined} size={avatarSize} />
<span className="truncate text-[var(--color-text)]">
{model ? model.name : t('button.select_model')} {providerName ? ' | ' + providerName : ''}
</span>
</div>
<ChevronsUpDown size={iconSize} color="var(--color-icon)" />
</div>
<ChevronsUpDown size={14} color="var(--color-icon)" />
</Button>
)
}