feat(AgentSettings): add model selection to essential settings

- Make model prop optional in ModelAvatar component
- Ensure models array always returns an array in useModels hook
- Export SelectorProps type for reuse
- Add getProviderNameById utility function
- Introduce ModelLabel component for displaying model info
- Update AgentEssentialSettings to include model selection dropdown
This commit is contained in:
icarus 2025-09-21 23:31:13 +08:00
parent f49d3791b6
commit f45b744318
6 changed files with 63 additions and 23 deletions

View File

@ -5,7 +5,7 @@ import { first } from 'lodash'
import { FC } from 'react'
interface Props {
model: Model
model?: Model
size: number
props?: AvatarProps
className?: string

View File

@ -34,7 +34,7 @@ interface MultipleSelectorProps<V> extends BaseSelectorProps<V> {
onChange: (value: V[]) => void
}
type SelectorProps<V> = SingleSelectorProps<V> | MultipleSelectorProps<V>
export type SelectorProps<V> = SingleSelectorProps<V> | MultipleSelectorProps<V>
const Selector = <V extends string | number>({
options,

View File

@ -12,7 +12,7 @@ export const useModels = (filter?: ApiModelsFilter) => {
}, [client, filter])
const { data, error, isLoading } = useSWR(path, fetcher)
return {
models: data?.data,
models: data?.data ?? [],
error,
isLoading
}

View File

@ -1,11 +1,12 @@
import { HStack } from '@renderer/components/Layout'
import { useModels } from '@renderer/hooks/agents/useModels'
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
import { AgentEntity, UpdateAgentForm } from '@renderer/types'
import { Input } from 'antd'
import { FC, useState } from 'react'
import { Input, Select } from 'antd'
import { DefaultOptionType } from 'antd/es/select'
import { FC, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AgentLabel, SettingsContainer, SettingsItem, SettingsTitle } from './shared'
import { AgentLabel, ModelLabel, SettingsContainer, SettingsItem, SettingsTitle } from './shared'
interface AgentEssentialSettingsProps {
agent: AgentEntity | undefined | null
@ -15,6 +16,7 @@ interface AgentEssentialSettingsProps {
const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update }) => {
const { t } = useTranslation()
const [name, setName] = useState<string>((agent?.name ?? '').trim())
const { models } = useModels({ providerType: 'anthropic' })
const onUpdate = () => {
if (!agent) return
@ -22,6 +24,13 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
update(_agent)
}
const modelOptions = useMemo(() => {
return models.map((model) => ({
value: model.id,
label: <ModelLabel model={model} />
})) satisfies DefaultOptionType[]
}, [models])
if (!agent) return null
return (
@ -30,21 +39,27 @@ const AgentEssentialSettings: FC<AgentEssentialSettingsProps> = ({ agent, update
<SettingsTitle>{t('agent.type.label')}</SettingsTitle>
<AgentLabel type={agent.type} />
</SettingsItem>
<SettingsItem>
<SettingsItem inline>
<SettingsTitle>{t('common.name')}</SettingsTitle>
<HStack gap={8} alignItems="center">
<Input
placeholder={t('common.assistant') + t('common.name')}
value={name}
onChange={(e) => setName(e.target.value)}
onBlur={() => {
if (name !== agent.name) {
onUpdate()
}
}}
style={{ flex: 1 }}
/>
</HStack>
<Input
placeholder={t('common.agent_one') + t('common.name')}
value={name}
onChange={(e) => setName(e.target.value)}
onBlur={() => {
if (name !== agent.name) {
onUpdate()
}
}}
className="max-w-80 flex-1"
/>
</SettingsItem>
<SettingsItem inline className="gap-8">
<SettingsTitle>{t('common.model')}</SettingsTitle>
<Select
options={modelOptions}
className="max-w-80 flex-1"
placeholder={t('common.placeholders.select.model')}
/>
</SettingsItem>
</SettingsContainer>
)

View File

@ -1,7 +1,8 @@
import { Avatar, AvatarProps, cn } from '@heroui/react'
import { getAgentAvatar } from '@renderer/config/agent'
import { getModelLogo } from '@renderer/config/models'
import { getAgentTypeLabel } from '@renderer/i18n/label'
import { AgentType } from '@renderer/types'
import { AgentType, ApiModel } from '@renderer/types'
import React from 'react'
import { SettingDivider } from '..'
@ -48,7 +49,7 @@ export const SettingsItem: React.FC<SettingsItemProps> = ({
<>
<div
{...props}
className={cn('flex flex-col', inline ? 'flex-row items-center justify-between gap-2' : undefined, className)}>
className={cn('flex flex-col', inline ? 'flex-row items-center justify-between gap-4' : undefined, className)}>
{children}
</div>
{divider && <SettingDivider />}
@ -63,3 +64,18 @@ export const SettingsContainer: React.FC<React.ComponentPropsWithRef<'div'>> = (
</div>
)
}
export interface ModelLabelProps extends Omit<React.ComponentPropsWithRef<'div'>, 'children'> {
model: ApiModel
}
export const ModelLabel: React.FC<ModelLabelProps> = ({ model, className, ...props }) => {
return (
<div className={cn('flex items-center gap-1', className)} {...props}>
<Avatar src={getModelLogo(model.id)} className="h-4 w-4" />
<span>
{model.name} | {model.provider_name}
</span>
</div>
)
}

View File

@ -12,6 +12,15 @@ export function getProviderName(model?: Model) {
return getFancyProviderName(provider)
}
export function getProviderNameById(pid: string) {
const provider = store.getState().llm.providers.find((p) => p.id === pid)
if (provider) {
return getFancyProviderName(provider)
} else {
return 'Unknown Provider'
}
}
export function getProviderByModel(model?: Model) {
const id = model?.provider
const provider = store.getState().llm.providers.find((p) => p.id === id)