mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(desktop): show custom (non-curated) model in Settings model pickers
A Radix <Select> renders a blank trigger when its `value` matches no <SelectItem>. The Settings model pickers built their options solely from each provider's curated `models` list, so a model added via config that isn't in that list (e.g. anthropic/claude-opus-4.7 on nous) selected nothing and showed an empty selector. Union the active value into the options via a small `withActive` helper, applied to the main, auxiliary, MoA reference, and MoA aggregator model selects so the configured model always stays visible and selectable.
This commit is contained in:
parent
7475d125d2
commit
a6ae179f43
2 changed files with 47 additions and 4 deletions
|
|
@ -78,6 +78,12 @@ const AUX_TASKS: readonly AuxTaskMeta[] = [
|
|||
|
||||
const NO_PROVIDERS: readonly ModelOptionProvider[] = [{ name: '—', slug: '', models: [] }]
|
||||
|
||||
// Radix <Select> renders a blank trigger when `value` matches no <SelectItem>.
|
||||
// A custom model (e.g. one added via config that isn't in the provider's
|
||||
// curated list) would vanish — surface the active value so it stays selectable.
|
||||
export const withActive = (models: readonly string[], active: string): readonly string[] =>
|
||||
active && !models.includes(active) ? [active, ...models] : models
|
||||
|
||||
interface StaleAuxWarningProps {
|
||||
applying: boolean
|
||||
onReset: () => void
|
||||
|
|
@ -555,7 +561,7 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
<SelectValue placeholder={m.model} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(selectedProviderModels.length ? selectedProviderModels : []).map(model => (
|
||||
{withActive(selectedProviderModels, selectedModel).map(model => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
|
|
@ -708,7 +714,7 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
<SelectValue placeholder={m.model} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(auxDraftProviderModels.length ? auxDraftProviderModels : []).map(model => (
|
||||
{withActive(auxDraftProviderModels, auxDraft.model).map(model => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
|
|
@ -880,7 +886,7 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
<SelectValue placeholder={m.model} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{modelsForProvider(slot.provider).map(model => (
|
||||
{withActive(modelsForProvider(slot.provider), slot.model).map(model => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
|
|
@ -957,7 +963,10 @@ export function ModelSettings({ onMainModelChanged }: ModelSettingsProps) {
|
|||
<SelectValue placeholder={m.model} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{modelsForProvider(currentMoaPreset.aggregator.provider).map(model => (
|
||||
{withActive(
|
||||
modelsForProvider(currentMoaPreset.aggregator.provider),
|
||||
currentMoaPreset.aggregator.model
|
||||
).map(model => (
|
||||
<SelectItem key={model} value={model}>
|
||||
{model}
|
||||
</SelectItem>
|
||||
|
|
|
|||
34
apps/desktop/src/app/settings/with-active.test.ts
Normal file
34
apps/desktop/src/app/settings/with-active.test.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { withActive } from './model-settings'
|
||||
|
||||
// A Radix <Select> shows a blank trigger when its `value` matches no
|
||||
// <SelectItem>. `withActive` guarantees the controlled value is always
|
||||
// representable so a config-only / custom model never renders blank.
|
||||
describe('withActive', () => {
|
||||
const curated = ['hermes-4', 'hermes-4-mini']
|
||||
|
||||
it('prepends a custom model missing from the curated list', () => {
|
||||
expect(withActive(curated, 'anthropic/claude-opus-4.7')).toEqual([
|
||||
'anthropic/claude-opus-4.7',
|
||||
...curated
|
||||
])
|
||||
})
|
||||
|
||||
it('leaves the list untouched when the active model is already curated', () => {
|
||||
expect(withActive(curated, 'hermes-4')).toEqual(curated)
|
||||
})
|
||||
|
||||
it('does not inject an empty active value', () => {
|
||||
expect(withActive(curated, '')).toEqual(curated)
|
||||
})
|
||||
|
||||
it('surfaces the active model even when the curated list is empty', () => {
|
||||
expect(withActive([], 'anthropic/claude-opus-4.7')).toEqual(['anthropic/claude-opus-4.7'])
|
||||
})
|
||||
|
||||
it('keeps the active model selectable as the invariant', () => {
|
||||
const out = withActive(curated, 'custom/model')
|
||||
expect(out).toContain('custom/model')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue