Eric Hare cd4aa718e7 fix: stabilize model toggle and refresh Agent dropdown after provider changes (#13113)
* fix: stabilize model toggle and refresh Agent dropdown after provider changes

Two related regressions in the Model Provider management flow on
release-1.9.3:

1. Toggle bounce — rapid clicks on a model Switch flickered between on
   and off before settling. ``handleModelToggle`` performed an
   optimistic ``setQueryData`` on ``useGetEnabledModels`` but never
   cancelled in-flight refetches. With React Query defaults
   (``staleTime: 0``, ``refetchOnWindowFocus: true``), a background
   refetch could land mid-debounce and overwrite the optimistic state
   with stale server data, then the deferred mutation eventually
   corrected it. Fix: ``cancelQueries`` before each optimistic update,
   per the canonical TanStack Query optimistic-updates pattern.

2. Agent dropdown stayed stale after the provider modal closed —
   disabled models remained listed in the Agent's Language Model
   dropdown. The post-close ``isRefreshingAfterClose`` loading gate
   only waited for ``useGetModelProviders`` to settle, not
   ``useGetEnabledModels``. When the providers refetch finished first,
   the loading state cleared and ``groupedOptions`` ran against the
   still-stale enabled-models cache. Fix: track both queries'
   ``isFetching`` flags in the gate's effect.

No behavioral change to the sticky-default UX — the user's previously
selected model continues to show in the dropdown with the wrench
affordance when globally disabled, as designed.

Also narrows the pre-existing ``catch (error: any)`` patterns in the
edited hook file to ``unknown`` with a small shared ``getErrorMessage``
helper, satisfying the staged-file no-explicit-any pre-commit lint.

Tests:
- New ``useProviderConfiguration`` unit test asserts
  ``cancelQueries`` runs before ``setQueryData`` on toggle.
- Extended ``ModelInputComponent`` test covers the dual-query loading
  gate (providers settles first, enabled-models still in flight →
  loading persists; then enabled-models settles → loading clears).

* fix: re-apply pending overlay through entire mutation lifecycle

Addresses review feedback on #13113: ``cancelQueries`` only cancels
refetches that are already in flight at click time. A new
``useGetEnabledModels`` refetch can still start during the 1s
debounce window (or while the mutation is in flight) and overwrite
the optimistic cache with stale server state, producing the same
bounce we just fixed.

Two coordinated changes keep the optimistic state protected for the
entire pending-toggle window:

1. ``pendingModelToggles`` is no longer cleared upfront when the
   debounced flush sends a mutation. Instead, ``clearSentToggles``
   removes only the entries whose value still matches what we sent
   on ``onSettled`` / ``onError``. This preserves the overlay across
   the in-flight mutation window AND correctly handles the case
   where the user re-toggled the same model mid-flight (now a fresh
   intent that survives clearing).

2. A new ``useEffect`` subscribes to ``useGetEnabledModels`` and
   re-applies the pending overlay whenever the data emission drifts
   from the user's pending intent. This catches any refetch
   (window focus, mount, reconnect, stale-time expiry) that lands
   between click and ``onSettled``, regardless of when it started.

Tests:
- New ``re-applies the pending overlay when a refetch surfaces
  stale data`` test simulates a stale refetch and asserts the
  effect re-applies the optimistic overlay.
- New ``does not re-overlay when no toggles are pending`` test
  guards against spurious overlay calls on mount.

* fix: split overlay vs unsent buffers to prevent duplicate model toggle mutations

Addresses a follow-up race introduced by the previous commit: keeping
``pendingModelToggles`` populated through ``onSettled`` (so the
re-overlay effect could repel mid-flight refetches) also caused the
next flush to snapshot the in-flight entries again, sending duplicate
requests with non-deterministic success/failure ordering. The same
risk applied to ``flushPendingChanges`` on modal close.

Split the single buffer into two refs with single responsibilities:

  - ``overlayToggles``: the union of every toggle still protecting the
    UI. The re-overlay effect re-applies this whenever
    ``useGetEnabledModels`` emits new data. Drained per-key on
    ``onSettled``/``onError`` only when the overlay value still matches
    what we sent (a user re-toggle mid-flight becomes a fresh intent
    and must survive the clear).

  - ``unsentToggles``: the strict subset that has NOT been sent in a
    mutation yet, or was re-toggled since the last send. Drained
    immediately at flush time so subsequent flushes never resend an
    in-flight payload.

``handleModelToggle`` populates both buffers; flushes drain only
``unsentToggles`` and use ``overlayToggles`` for cache protection.

Two new unit tests:
- ``does not resend in-flight toggles when a new toggle is flushed``
  asserts that after toggling A then B (with A's mutation in flight),
  the second flush carries ONLY B.
- ``re-sends a model when the user re-toggles it after the previous
  flush fired`` asserts that re-toggling the same model produces a
  second mutation (the re-toggle is a fresh intent, not a duplicate).

* fix: drop sticky-default UX, treat disabled-selected model like any other

Removes the sticky-default carve-out so a globally-disabled model is
never shown in the Agent's Language Model dropdown, regardless of
whether it's the node's saved selection. Drops the Wrench/Configure
affordance — there's no per-trigger "this model isn't enabled for
your user" UX anymore; the dropdown is the single source of truth.

Changes in ``modelInputComponent``:

  - ``groupedOptions``: drop the ``isStickyNotEnabled`` filter
    bypass and the zero-provider import fallback. Disabled models
    never pass the filter, even when tagged ``not_enabled_locally``.

  - ``selectedModel``: drop the saved-value preservation branch.
    If the saved name isn't in ``flatOptions``, fall through to the
    first available option (or ``null`` if nothing is available).

  - Auto-select effect: fires whenever the saved value isn't in
    ``flatOptions``, not just when the value is empty. This
    realigns the node's stored value with the trigger so the
    rendered selection and the run-time selection don't diverge.

  - ``hasEnabledProviders``: redefined as "at least one model of
    this component's type is enabled across any provider"
    (derived from ``groupedOptions``). Routes a configured-but-
    all-disabled provider to the Setup Provider CTA — same UX as
    the never-configured case.

  - Remove the Wrench JSX, the ``showConfigureAffordance``
    derivation, and the now-orphaned ``hasProcessedEmptyRef``.

Tests:
  - ``renders the Setup Provider CTA when no models are enabled``
    (replaces the old disabled-combobox expectation).
  - ``auto-selects an available model when the saved value is
    globally disabled``.
  - ``renders the Setup Provider CTA when a provider is configured
    but all its models are disabled``.
  - ``never renders the Configure wrench affordance``.

* refactor: extract toggle queue into useModelToggleQueue hook

Addresses review I1 (file size + mixed responsibilities) and R1, R2,
R3, R4 in one pass.

I1 — Extract toggle-queue logic out of useProviderConfiguration. The
parent hook drops from 821 lines to 601 and now owns a single
responsibility (variable CRUD + provider lifecycle). The toggle queue
— overlay/unsent buffers, debounced flush, awaitable flush, optimistic
cache management, re-overlay effect — lives in a new 292-line
useModelToggleQueue.ts with one clear responsibility.

R1 — Inside the new hook, DRY the two flush variants behind shared
helpers:
  - ``buildAndConsumeToggleBatch()`` snapshots unsentToggles into the
    mutation payload, captures the pre-toggle cache for rollback, and
    drains unsentToggles atomically.
  - ``rollbackToggleBatch()`` drains overlay BEFORE restoring
    previousData (load-bearing order — otherwise the re-overlay effect
    would re-apply the stale overlay over the rollback).
The two flush callers now differ only in ``mutate`` vs awaitable
``mutateAsync`` plumbing.

R2 — Explicit loop-guard comment on the drift check inside the
re-overlay effect. Names the ``drifted`` short-circuit as the
termination condition so a future refactor that "simplifies" to an
unconditional re-apply doesn't silently introduce a render loop.

R3 — Adversarial coverage for the mutation error path:
  - ``rolls back to previousData when the toggle mutation fails``
  - ``does not re-apply the overlay after a failed mutation drains it``
    (asserts the drain-before-rollback ordering)
  - ``preserves a mid-flight re-toggle when the original mutation
    fails`` (asserts a user re-toggle survives the originating
    mutation's failure)

R4 — ``flushPendingChanges`` now invalidates the affected queries
inline on success, instead of relying on the caller's
``refreshAllModelInputs`` for the load-bearing invalidation. The
caller's refresh remains as an additive per-node template refresh.

Test infra: moved toggle-queue tests from
``useProviderConfiguration.test.tsx`` to a dedicated
``useModelToggleQueue.test.tsx`` that exercises the extracted hook
directly. Debounce mock changed from synchronous pass-through to
explicit ``runDebounced()`` so individual tests can choose whether to
exercise the debounced path or the awaitable ``flushPendingChanges``
path.

95/95 tests pass (87 prior + new R3 coverage + R4 success-path
invalidation).
2026-05-13 14:17:31 -07:00
2026-04-23 17:49:53 -07:00
2026-04-23 17:49:53 -07:00
2026-04-13 23:23:37 +00:00

Langflow logo

Release Notes PyPI - License PyPI - Downloads Twitter YouTube Channel Discord Server Ask DeepWiki

Langflow is a powerful platform for building and deploying AI-powered agents and workflows. It provides developers with both a visual authoring experience and built-in API and MCP servers that turn every workflow into a tool that can be integrated into applications built on any framework or stack. Langflow comes with batteries included and supports all major LLMs, vector databases and a growing library of AI tools.

Highlight features

  • Visual builder interface to quickly get started and iterate.
  • Source code access lets you customize any component using Python.
  • Interactive playground to immediately test and refine your flows with step-by-step control.
  • Multi-agent orchestration with conversation management and retrieval.
  • Deploy as an API or export as JSON for Python apps.
  • Deploy as an MCP server and turn your flows into tools for MCP clients.
  • Observability with LangSmith, LangFuse and other integrations.
  • Enterprise-ready security and scalability.

🖥️ Langflow Desktop

Langflow Desktop is the easiest way to get started with Langflow. All dependencies are included, so you don't need to manage Python environments or install packages manually. Available for Windows and macOS.

📥 Download Langflow Desktop

Quickstart

Requires Python 3.103.13 and uv (recommended package manager).

Install

From a fresh directory, run:

uv pip install langflow -U

The latest Langflow package is installed. For more information, see Install and run the Langflow OSS Python package.

Run

To start Langflow, run:

uv run langflow run

Langflow starts at http://127.0.0.1:7860.

That's it! You're ready to build with Langflow! 🎉

📦 Other install options

Run from source

If you've cloned this repository and want to contribute, run this command from the repository root:

make run_cli

For more information, see DEVELOPMENT.md.

Docker

Start a Langflow container with default settings:

docker run -p 7860:7860 langflowai/langflow:latest

Langflow is available at http://localhost:7860/. For configuration options, see the Docker deployment guide.

🛡️ Security

For security information, see our Security Policy.

🚀 Deployment

Langflow is completely open source and you can deploy it to all major deployment clouds. To learn how to deploy Langflow, see our Langflow deployment guides.

Stay up-to-date

Star Langflow on GitHub to be instantly notified of new releases.

Star Langflow

👋 Contribute

We welcome contributions from developers of all levels. If you'd like to contribute, please check our contributing guidelines and help make Langflow more accessible.


Star History Chart

❤️ Contributors

langflow contributors

Description
Langflow is a powerful tool for building and deploying AI-powered agents and workflows.
Readme MIT 2.3 GiB
Languages
Python 64.5%
TypeScript 23.4%
JavaScript 11.4%
CSS 0.3%
Makefile 0.2%
Other 0.1%