LocalMode
React

Provider Fallback

React hook for per-capability Chrome Built-in AI ↔ Transformers.js provider resolution with truthful badge provenance.

Provider Fallback Hook

See it in action

Try the Writing Tools blocks for a working demo — the Write / Translate / Summarize / Complete blocks resolve Chrome Built-in AI independently per capability and fall back to Transformers.js behind an explicit download gate, rendering a truthful provider badge derived from this hook.

useProviderFallback

Resolve each capability (Summarizer, Translator, AI-edit, fill-mask) to Chrome Built-in AI or Transformers.js. Detection is independent per capability and reads the browser globals directly, so one capability can be Chrome-AI-served while another falls back in the same session. Every provider package is loaded via a dynamic import() inside the resolution path, so @localmode/react gains no hard dependency on @localmode/chrome-ai or @localmode/transformers — nothing about a provider ships until a capability first resolves. Resolution is lazy and session-cached, and each result carries truthful resolution provenance for a badge.

import { useProviderFallback, providerName } from '@localmode/react';

function SummarizeTab() {
  const { resolveSummarizer, resolution, isResolving } = useProviderFallback();

  async function run(text: string) {
    const { model } = await resolveSummarizer({
      chromeStyle: 'key-points',
      length: 'medium',
      fallbackModelId: 'Xenova/distilbart-cnn-6-6',
    });
    return summarize({ model, text });
  }

  return resolution ? <Badge provider={providerName(resolution.provider)} tier={resolution.tier} /> : null;
}

Options

Prop

Type

In the browser, inject static loader overrides

The default loadChromeAI / loadTransformers loaders use a Function-constructed import() so @localmode/react keeps no hard dependency on the provider packages. That indirection is opaque to bundlers, so a browser build can't resolve the bare specifier at runtime. When you consume this hook in the browser, pass loaders with literal imports the bundler can see:

useProviderFallback({
  loadChromeAI: () => import('@localmode/chrome-ai'),
  loadTransformers: () => import('@localmode/transformers'),
});

Return Value

Prop

Type

Chrome availability & the download gate

Chrome's built-in AI APIs can exist while their on-device model has not been fetched. In that state availability() reports 'downloadable', and Chrome refuses to start the download outside a user activation — so a button is the only way to trigger it.

const {
  chromeAvailability,
  refreshChromeAvailability,
  requestChromeDownload,
  chromeDownloadProgress,
  downloadingCapability,
} = useProviderFallback({ loadChromeAI, loadTransformers });

// Probing downloads nothing.
useEffect(() => {
  void refreshChromeAvailability('summarize', { chromeStyle: 'tldr', length: 'medium' });
}, [refreshChromeAvailability]);

// Must run from a click.
<button onClick={() => void requestChromeDownload('summarize', { chromeStyle: 'tldr', length: 'medium' })}>
  Download model
</button>;

ChromeAIAvailability is 'available' | 'downloadable' | 'downloading' | 'unavailable' | 'unsupported' (the last is ours, meaning the API is absent). ChromeCapability is 'summarize' | 'translate' | 'edit' — fill-mask has no Chrome equivalent. Translator availability is per language pair, so pass { source, target } and re-probe whenever the pair changes.

The ui/local-first/chrome-ai-download-gate registry primitive renders this state machine — button, progress, and the terminal unsupported / cannot-run states — from exactly these fields.

Resolver Params

Each resolver takes exactly the params its capability needs, so the split Write / Translate / Summarize / Complete surfaces each import only one. resolveFillMask takes a plain modelId: string (no Chrome equivalent).

ResolveSummarizerParams

Prop

Type

ResolveTranslatorParams

Prop

Type

ResolveEditEngineParams

Prop

Type

ResolvedModel and provenance

Every resolver returns a ResolvedModel<M> — the concrete model instance plus the provenance a badge derives from — and updates the hook's resolution, so a badge reflects what actually serves requests, never the detection probe alone.

Prop

Type

Standalone detectors and badge helpers

The detection functions are exported standalone (all async, all returning a ProviderId of 'chrome-ai' | 'transformers'). They read the same browser surfaces the @localmode/chrome-ai detectors read, so no provider import is needed to detect.

FunctionSignatureBehavior
detectSummarizerProvider(params?: ChromeCapabilityParams) => Promise<ProviderId>Gated on Summarizer.availability() === 'available' — presence alone is not enough, since a merely-downloadable model would throw at create(). Every other state falls back to 'transformers'.
detectTranslatorProvider(params?: ChromeCapabilityParams) => Promise<ProviderId>Gated on Translator.availability() === 'available' for the given language pair (packs download per directed pair). Every other state falls back to 'transformers'.
probeChromeAvailability(capability, params?, timeoutMs?) => Promise<ChromeAIAvailability>Chrome's raw model state. Returns 'unsupported' only when the API is genuinely absent, and rethrows a bad-option TypeError rather than mislabelling the browser. Raced against a 3s deadline (CHROME_AVAILABILITY_TIMEOUT_MS) because Translator.availability() never settles on some builds; a timeout reports 'unavailable'.
downloadChromeModel(capability: ChromeCapability, params?: ChromeCapabilityParams, onProgress?: (p: number) => void) => Promise<ChromeAIAvailability>Triggers Chrome's one-time, browser-wide download. Must be called from a user activation. Resolves to the post-download availability.
detectPromptProvider() => Promise<ProviderId>Gated on LanguageModel.availability() === 'available' — presence alone is not enough, which prevents committing to a merely-downloadable Gemini Nano. Every other state falls back to 'transformers'. The legacy self.ai.languageModel surface (no availability()) trusts presence.
providerTier(provider: ProviderId) => ProviderTierMaps 'chrome-ai''built-in', 'transformers''download'.
providerName(provider: ProviderId) => stringHuman-readable name: 'Chrome AI' or 'Transformers.js'.
import { detectPromptProvider, providerName, providerTier } from '@localmode/react';

const provider = await detectPromptProvider(); // 'chrome-ai' only when Gemini Nano is actually 'available'
const label = providerName(provider); // 'Chrome AI' | 'Transformers.js'
const tier = providerTier(provider); // 'built-in' | 'download'

For the Chrome Built-in AI provider (Summarizer, Translator, Prompt APIs) see the Chrome AI guide; for the Transformers.js fallback models see the Transformers provider guide.

Blocks

AppDescriptionLinks
Writing ToolsPer-capability Chrome AI ⇄ Transformers.js resolution with a truthful provider-fallback badge via useProviderFallbackLive block · Source

On this page