mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
fix(desktop): keep draft fallback rows across autosave echo
Add fallback only updates local editor state; complete pairs are filtered before onChange. The post-#7b5ba205 resync effect then saw the unchanged persisted chain and wiped the draft — button looked dead. Ignore value updates that match the last chain we emitted; still resync on real external changes (profile/config reload). Co-authored-by: HexLab98 <liruixinch@outlook.com>
This commit is contained in:
parent
af250d8494
commit
3615545bca
2 changed files with 41 additions and 5 deletions
|
|
@ -110,4 +110,19 @@ describe('FallbackModelsField', () => {
|
|||
|
||||
await waitFor(() => expect(screen.getAllByLabelText('Remove')).toHaveLength(1))
|
||||
})
|
||||
|
||||
it('keeps a draft row visible after autosave re-renders the same persisted chain', async () => {
|
||||
const onChange = vi.fn()
|
||||
const rerender = await renderFieldWithRerender([], onChange)
|
||||
|
||||
fireEvent.click(screen.getByText('Add fallback'))
|
||||
|
||||
expect(onChange.mock.calls.at(-1)?.[0]).toEqual([])
|
||||
expect(screen.getAllByLabelText('Remove')).toHaveLength(1)
|
||||
|
||||
// Parent autosave echo — same complete chain, new array identity.
|
||||
rerender([])
|
||||
|
||||
await waitFor(() => expect(screen.getAllByLabelText('Remove')).toHaveLength(1))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
|
|
@ -40,6 +40,14 @@ function normalizeEntries(value: unknown): FallbackEntry[] {
|
|||
})
|
||||
}
|
||||
|
||||
function completeEntries(rows: FallbackEntry[]): FallbackEntry[] {
|
||||
return rows.filter(entry => entry.provider && entry.model)
|
||||
}
|
||||
|
||||
function entriesEqual(a: FallbackEntry[], b: FallbackEntry[]): boolean {
|
||||
return a.length === b.length && a.every((entry, index) => entry.provider === b[index]?.provider && entry.model === b[index]?.model)
|
||||
}
|
||||
|
||||
/**
|
||||
* Structured editor for the top-level `fallback_providers` config list — a
|
||||
* chain of `{provider, model}` pairs tried in order when the default model
|
||||
|
|
@ -69,16 +77,29 @@ export function FallbackModelsField({
|
|||
const providers = (modelOptions.data?.providers ?? []).filter(provider => provider.slug)
|
||||
|
||||
const [rows, setRows] = useState<FallbackEntry[]>(() => normalizeEntries(value))
|
||||
// Last complete chain we emitted (or seeded). Autosave echoes the same
|
||||
// filtered list back through `value`; ignore that echo so draft rows stay.
|
||||
const lastEmittedRef = useRef(normalizeEntries(value))
|
||||
|
||||
// Settings can reload after a profile/config change while this component
|
||||
// stays mounted. Avoid displaying or saving the previous profile's chain.
|
||||
// Resync on real external changes (profile switch / config reload). Skip
|
||||
// when `value` is just our own commit echoing through the parent.
|
||||
useEffect(() => {
|
||||
setRows(normalizeEntries(value))
|
||||
const persisted = normalizeEntries(value)
|
||||
|
||||
if (entriesEqual(persisted, lastEmittedRef.current)) {
|
||||
return
|
||||
}
|
||||
|
||||
lastEmittedRef.current = persisted
|
||||
setRows(persisted)
|
||||
}, [value])
|
||||
|
||||
const commit = (next: FallbackEntry[]) => {
|
||||
const complete = completeEntries(next)
|
||||
|
||||
setRows(next)
|
||||
onChange(next.filter(entry => entry.provider && entry.model))
|
||||
lastEmittedRef.current = complete
|
||||
onChange(complete)
|
||||
}
|
||||
|
||||
const updateRow = (index: number, patch: Partial<FallbackEntry>) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue