| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(config): add CLI arg to specify config file This allows users to create simple "profiles" via separate `invokeai.yaml` files. - Remove `InvokeAIAppConfig.set_root()`, it's extraneous - Remove `InvokeAIAppConfig.merge_from_file()`, it's extraneous - Add `--config` to the app arg parser, add `InvokeAIAppConfig._config_file`, and consume in the config singleton getter - `InvokeAIAppConfig.init_file_path` -> `InvokeAIAppConfig.config_file_path` | 2 年前 | |
feat(installer): remove updater Updating should always be done via the installer. We initially planned to only deprecate the updater, but given the scale of changes for v4, there's no point in waiting to remove it entirely. | 2 年前 | |
fix(ui): stop range-based fetching hooks from spinning in a render loop (#9439) * fix(ui): stop range-based fetching hooks from spinning in a render loop `fetchItems` cleared the accumulated ranges with `setPendingRanges([])`, and `pendingRanges` is a dependency of the effect that calls `fetchItems`. A fresh `[]` is a new identity every time, so the effect re-ran, re-armed the 500ms throttle, and cleared again — a self-sustaining render loop that ran as fast as the throttle allowed, with no user input, for as long as the gallery grid was mounted. Clear with the shared stable `EMPTY_ARRAY` reference instead, so React bails out rather than re-running the effect. The queue variant returned early — before clearing — when nothing was uncached, which happened to prevent the loop while everything was cached, at the cost of letting ranges accumulate for the lifetime of the list and growing the scan on every pass. It now clears on both paths, with the stable reference doing the work of stopping the loop. Retry on failure explicitly, because the loop was doing it accidentally. These bulk fetches are the only fetcher for their rows: `ImageAtPosition` and `QueueItemAtPosition` both consume the cache with `skip: isUninitialized`, so a row whose DTO never arrived does not fetch for itself, and images have no retry affordance. Without this, a transient failure would leave placeholders until the user happened to scroll, where before the loop re-tried until it succeeded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ui): regression tests for the range-based fetching render loop Render both hooks with React act + fake timers in a happy-dom environment (scoped per-file via a @vitest-environment docblock; happy-dom is the only new dev dependency) and mock only the thin API-endpoint modules, so the tests exercise the real state/effect/throttle cycle the fix changed. Covered per hook: - a reported range fetches its uncached items once, then renders and fetches both go quiet (the pre-fix loop re-rendered every throttle window forever, and in the gallery hook ran from mount even with nothing to fetch) - items that never land in the cache (deleted image, multiuser ownership filter) are not re-requested indefinitely — bounded, then quiet, where the pre-fix loop was a permanent one-request-per-window stream - a failed bulk fetch is retried until it succeeds, then goes quiet — the explicit replacement for the retry the loop provided accidentally - every range reported within a throttle window is fetched, not just the last (the pendingRanges accumulation onRangeChanged exists for) - handled ranges are dropped, not accumulated: an item evicted from a long-handled range is not re-requested by later passes (the queue hook's pre-fix early return without clearing regressed exactly this) - new ranges after settling still fetch, and enabled=false fetches nothing The time-advance helper steps in small increments with an act flush per step; a single long advance would defer effect re-runs to the end of the act scope and break the very feedback cycle (state update -> effect -> throttle -> fetch) the suite exists to detect. Mutation-verified: reverting the EMPTY_ARRAY clears, restoring the queue hook's early return, dropping onRangeChanged's accumulation, or neutering the retry catch each makes at least one test fail; all pass with the fix in place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): bound the range-fetch retry with backoff and coalesced ranges Review feedback on the retry added in this PR: restoring the failed ranges immediately meant a sustained backend outage produced a request every throttle window forever, the restored state grew by a duplicate range per cycle, and `prev.length > 0 ? prev : ranges` dropped a failed range whenever another had been reported in the meantime. Replace the immediate restore with a shared useBoundedRangeRetry hook: - Exponential backoff between retries (1s, 2s, 4s, 8s, capped at 16s), giving up after 5 consecutive scheduled retries, so a sustained failure terminates instead of storming a backend that is trying to come back up. - Failed ranges accumulate as a coalesced (sorted, disjoint) union, and the restore merges them into whatever is pending instead of choosing one side, so nothing is dropped and nothing grows without bound. - A new range report resets the retry budget: fresh user input revives a list that gave up, and rows still in view are re-reported by virtuoso when the user scrolls back anyway. Tests: negative-path coverage for both hooks (sustained failure terminates; scrolling revives a given-up list; a range that failed mid-scroll is recovered) plus unit tests for coalesceRanges. Mutation-verified: removing the backoff/cap, the budget reset, the merge-on-restore, or the retry itself each makes at least one test fail; all 30 pass with the change in place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): heal an abandoned range fetch on reconnect, and bound the retry's lifetime Round-2 review findings on the bounded range-fetch retry. - Giving up was permanent. The budget ends ~31s after the first failure, but an InvokeAI restart routinely takes longer, and for an idle user nothing re-arms it: `imageNames` keeps its identity through a reconnect refetch, `enabled` (`!isLoading`) does not toggle on a refetch, and in production `socketConnected` only invalidates `FetchOnReconnect` when the queue status changed. Ranges abandoned by an exhausted budget are now parked as a coalesced union instead of dropped, and restored on the next signal that the backend is answering: a socket reconnect, a successful fetch, or a fresh range report. - A fetch that rejected after unmount armed a backoff timer no cleanup could reach. The retry state now tracks mount status and drops late failures. - The `!enabled` guard returned before the clear — the same accumulate-forever pattern this PR fixes on the cached path. Both hooks now clear on that path. - `restoreRanges` is read through a ref, so an unstable callback can no longer churn `onFetchFailure` and the fetch effect behind it. Tests: reconnect healing, a post-unmount rejection arming no timer, and the disabled-window accumulation case in both hook suites, plus the queue suite's missing mount-time no-loop test. Each is mutation-verified against the fix it covers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWxRMfb5wDBi6isQ6XKrgE * fix(ui): stop losing restored ranges to the throttle, and floor the reconnect re-arm Adversarial review of the previous commit found two real defects. Restored ranges could be lost permanently. `restoreRanges` dispatches a functional `setPendingRanges`, but the fetch pass ended with an absolute `setPendingRanges(EMPTY_ARRAY)`. A backoff timer and the throttle's trailing edge can expire in the same event-loop turn, so both land in one React batch: the absolute update runs last, the final state equals the base, React bails out of the re-render, and the ranges are gone with nothing left to re-report them. Reproduced deterministically in both hooks (fail a range, scroll elsewhere 600-1000ms later, recover: the first range is never fetched again — grey rows until the user scrolls back). The clear now only fires when `pendingRanges` is still the array that pass consumed, so it is a no-op once the state has moved on. The existing scroll-recovery test was pinned to a delay that happened to miss this window; it now sweeps 500-1250ms and fails at three of five without the fix. The reconnect signal made the bounded retry unbounded. `attempts` was zeroed on every `$isConnected` transition, even with nothing parked, so a socket that keeps completing a handshake while REST stays broken (crash-looping container, uvicorn accepting connections before startup finishes, a proxy splitting websocket and REST across replicas) pinned the backoff at its shortest delay: 300 requests over five minutes of 5s flapping, against a design intent of 12. The re-arm is now floored at one per 60s and only fires when there is something parked to heal — 70 requests in the same scenario. Also: the latest-callback ref moved to a layout effect so a restore firing before the passive flush sees the intended closure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWxRMfb5wDBi6isQ6XKrgE * test(ui): pin the scroll/success restore of parked ranges; drop a stray vitest artifact Review round 3, both findings. `useBoundedRangeRetry` restores parked ranges on three signals — a socket reconnect, a later successful fetch, and a fresh range report — but only the reconnect was pinned. `resumes retrying after giving up when the user scrolls` re-reports the same range, which `lastRange` re-fetches whether or not the parked set was restored, so deleting the restore block from `resetRetryBudget` left every test green. The new test parks a range under sustained failure, has the backend answer again with no socket transition (a transient proxy 502, where the websocket never drops), then scrolls to a disjoint range, and asserts both the parked and the new range are fetched. Mutation-verified: removing the restore fails it in both suites. Behaviour is unchanged; this is coverage only. Also drops `node_modules/.vite/vitest/.../results.json`, committed by accident — the root .gitignore had no `node_modules` entry (only the web app's does), so a repo-root `node_modules/` was untracked but not ignored. Added it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCitU6EFcp76AaauNfaWTz * test(ui): pin the parked-set clear and both coalescing call sites Round-4 review findings, both coverage-only: - 'empties the parked set when it heals' passed with the clear in takeAbandonedRanges deleted: with no second heal signal, a re-restore installs the same array reference while pendingRanges holds EMPTY_ARRAY, React bails out, and the fetch count stays flat either way. Both suites now scroll to a disjoint range after the heal and assert only the new range is requested — a parked set that outlived its restore rides along and fails the assertion. - Neither coalesceRanges call site was pinned: replacing either accumulation with a plain concat left every suite green, so the bounded-state property rested on the helper's unit tests alone. Two hook-level tests now observe what is actually handed to restoreRanges: failures merged while a retry is scheduled arrive as one coalesced union, and repeated post-exhaustion failures park as one. All three mutants now die by exactly the intended tests (2/1/1 failures). Full suite: 171 files, 2275 tests, five lints clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUz6V3apfDE3hCrhGfMDpt --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev> | 3 天前 | |
Apply black | 3 年前 |