mirror of
https://github.com/mengxi-ream/read-frog.git
synced 2026-04-30 01:56:46 +00:00
Compare commits
1 commit
codex/fix-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbacbf52a7 |
3 changed files with 67 additions and 3 deletions
5
.changeset/focused-provider-options.md
Normal file
5
.changeset/focused-provider-options.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@read-frog/extension": patch
|
||||
---
|
||||
|
||||
fix(options): preserve focused provider options drafts
|
||||
|
|
@ -22,17 +22,23 @@ vi.mock("@/components/ui/json-code-editor", () => ({
|
|||
JSONCodeEditor: ({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
onFocus,
|
||||
placeholder,
|
||||
}: {
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
onBlur?: () => void
|
||||
onFocus?: () => void
|
||||
placeholder?: string
|
||||
}) => (
|
||||
<textarea
|
||||
aria-label="provider-options-editor"
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onBlur={onBlur}
|
||||
onChange={event => onChange?.(event.target.value)}
|
||||
onFocus={onFocus}
|
||||
/>
|
||||
),
|
||||
}))
|
||||
|
|
@ -53,16 +59,22 @@ const baseProviderConfig: APIProviderConfig = {
|
|||
function ProviderOptionsFieldHarness({
|
||||
initialConfig,
|
||||
externalProviderOptions,
|
||||
submitDelayMs = 0,
|
||||
}: {
|
||||
initialConfig: APIProviderConfig
|
||||
externalProviderOptions?: Record<string, unknown>
|
||||
submitDelayMs?: number
|
||||
}) {
|
||||
const [providerConfig, setProviderConfig] = useState(initialConfig)
|
||||
const form = useAppForm({
|
||||
...formOpts,
|
||||
defaultValues: providerConfig,
|
||||
onSubmit: async ({ value }) => {
|
||||
setProviderConfig(value)
|
||||
if (submitDelayMs > 0) {
|
||||
await new Promise(resolve => window.setTimeout(resolve, submitDelayMs))
|
||||
}
|
||||
|
||||
setProviderConfig(submitDelayMs > 0 ? structuredClone(value) : value)
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -105,6 +117,7 @@ describe("providerOptionsField", () => {
|
|||
render(<ProviderOptionsFieldHarness initialConfig={baseProviderConfig} />)
|
||||
|
||||
const editor = screen.getByLabelText("provider-options-editor")
|
||||
fireEvent.focus(editor)
|
||||
fireEvent.change(editor, { target: { value: "{\"reasoningEffort\":\"minimal\"}" } })
|
||||
|
||||
await act(async () => {
|
||||
|
|
@ -115,6 +128,29 @@ describe("providerOptionsField", () => {
|
|||
expect(screen.getByLabelText("provider-options-editor")).toHaveValue("{\"reasoningEffort\":\"minimal\"}")
|
||||
})
|
||||
|
||||
it("keeps focused draft edits when a delayed autosave echo arrives", async () => {
|
||||
render(<ProviderOptionsFieldHarness initialConfig={baseProviderConfig} submitDelayMs={100} />)
|
||||
|
||||
const editor = screen.getByLabelText("provider-options-editor")
|
||||
fireEvent.focus(editor)
|
||||
fireEvent.change(editor, { target: { value: "{\"reasoningEffort\":\"minimal\"}" } })
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(500)
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
fireEvent.change(editor, { target: { value: "{\"reasoningEffort\":\"low\"}" } })
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(100)
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
expect(screen.getByLabelText("provider-options-editor")).toHaveValue("{\"reasoningEffort\":\"low\"}")
|
||||
})
|
||||
|
||||
it("shows the matched recommended provider options as the placeholder when the value is empty", () => {
|
||||
render(<ProviderOptionsFieldHarness initialConfig={baseProviderConfig} />)
|
||||
|
||||
|
|
@ -180,7 +216,9 @@ describe("providerOptionsField", () => {
|
|||
)
|
||||
|
||||
const editor = screen.getByLabelText("provider-options-editor")
|
||||
fireEvent.focus(editor)
|
||||
fireEvent.change(editor, { target: { value: "{" } })
|
||||
fireEvent.blur(editor)
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByRole("button", { name: "apply-external" }))
|
||||
await Promise.resolve()
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export const ProviderOptionsField = withForm({
|
|||
const [jsonInput, setJsonInput] = useState(() => externalJson)
|
||||
const lastCommittedJsonRef = useRef(externalJson)
|
||||
const pendingEditorCommitRef = useRef(false)
|
||||
const editorFocusedRef = useRef(false)
|
||||
|
||||
const syncJsonInput = useEffectEvent((nextJson: string) => {
|
||||
// eslint-disable-next-line react/set-state-in-effect
|
||||
|
|
@ -55,6 +56,18 @@ export const ProviderOptionsField = withForm({
|
|||
return jsonInput
|
||||
})
|
||||
|
||||
const handleJsonInputChange = useCallback((nextJson: string) => {
|
||||
setJsonInput(nextJson)
|
||||
}, [])
|
||||
|
||||
const handleEditorFocus = useCallback(() => {
|
||||
editorFocusedRef.current = true
|
||||
}, [])
|
||||
|
||||
const handleEditorBlur = useCallback(() => {
|
||||
editorFocusedRef.current = false
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
resetSyncStateForProvider()
|
||||
}, [providerConfig.id])
|
||||
|
|
@ -66,9 +79,15 @@ export const ProviderOptionsField = withForm({
|
|||
}
|
||||
|
||||
pendingEditorCommitRef.current = false
|
||||
|
||||
const currentJsonInput = readJsonInput()
|
||||
if (editorFocusedRef.current && currentJsonInput !== lastCommittedJsonRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
lastCommittedJsonRef.current = externalJson
|
||||
|
||||
if (readJsonInput() !== externalJson) {
|
||||
if (currentJsonInput !== externalJson) {
|
||||
syncJsonInput(externalJson)
|
||||
}
|
||||
}, [providerConfig.providerOptions, externalJson])
|
||||
|
|
@ -127,7 +146,9 @@ export const ProviderOptionsField = withForm({
|
|||
</FieldLabel>
|
||||
<JSONCodeEditor
|
||||
value={jsonInput}
|
||||
onChange={setJsonInput}
|
||||
onChange={handleJsonInputChange}
|
||||
onFocus={handleEditorFocus}
|
||||
onBlur={handleEditorBlur}
|
||||
placeholder={placeholderText}
|
||||
hasError={!!jsonError}
|
||||
height="150px"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue