prompt api polyfillprompt api firefoxfallback ia browser

Polyfill da Prompt API: como dar fallback para Firefox e Safari

Prompt API Brasil

Polyfill da Prompt API: como dar fallback para Firefox e Safari

O prompt-api-polyfill é uma biblioteca do Google Chrome Labs (maio de 2026) que implementa a interface completa da Prompt API em browsers que não têm IA on-device. Firefox, Safari, Chrome velho — o polyfill redireciona a inferência para backends cloud (Gemini API, OpenAI) ou modelos locais via Transformers.js. Você escreve código contra a API padrão e funciona em qualquer browser moderno. Sem if/else por browser. Sem gambiarras.

TL;DR

  • O polyfill oficial implementa window.LanguageModel em Firefox, Safari e Chrome antigo via backends cloud ou Transformers.js
  • Estratégia: on-device quando disponível (privacidade, offline), cloud como fallback (compatibilidade)
  • Código idêntico funciona cross-browser — progressive enhancement com feature detection

Se tem uma lição que eu levo de anos fazendo web: nunca aposte tudo em feature exclusiva de um browser. Já vi esse filme — ActiveX, prefixed CSS, APIs que morreram na praia. O polyfill resolve exatamente isso.

Diagrama de navegadores conectados a um nó de polyfill com fallback local e cloud.

O problema

A Prompt API funciona no Chrome. E só no Chrome. O cenário real:

BrowserStatusPosição
Chrome 148+✅ Estável com Gemini NanoPropositor
Edge✅ Developer Preview com Phi-4-mini / AionSuporte ativo
Firefox❌ Não implementaPosição contrária
Safari❌ Não implementaPosição contrária

Isso significa ~30% dos usuários web (Firefox + Safari) ficam de fora. Para aplicação em produção, ignorar um terço dos usuários não é opção.

Instalação e setup

npm install prompt-api-polyfill

O polyfill funciona em duas camadas: a interface (implementa window.LanguageModel) e o backend (direciona inferência para um provedor).

Backends disponíveis

BackendProcessamentoCustoPrivacidadeOffline
Gemini API (cloud)Servidor GooglePay-per-tokenDados saem
OpenAI API (cloud)Servidor OpenAIPay-per-tokenDados saem
Transformers.js (local)Browser do usuárioGrátisTotal

Com Gemini (cloud)

import config from './.env.json' with { type: 'json' };

window.GEMINI_CONFIG = config;

// Carregar APENAS quando necessário
if (!('LanguageModel' in window)) {
  await import('prompt-api-polyfill');
}

// A partir daqui, mesma API
const session = await LanguageModel.create();
const resposta = await session.prompt("Explique recursão em uma frase");

Com Transformers.js (100% local)

// .env.json
{
  "apiKey": "dummy",
  "device": "webgpu",
  "dtype": "q4f16",
  "modelName": "onnx-community/gemma-3-1b-it-ONNX-GQA"
}
window.TRANSFORMERSJS_CONFIG = config;

if (!('LanguageModel' in window)) {
  await import('prompt-api-polyfill');
}

// Mesma API — mas roda Transformers.js por baixo
const session = await LanguageModel.create();
const resposta = await session.prompt("Olá, mundo!");

O backend Transformers.js não suporta responseConstraint (structured output). Os backends cloud suportam.

Progressive enhancement: o padrão correto

O approach certo é começar com funcionalidade básica e melhorar quando IA está disponível:

class AIAssistant {
  #session = null;
  #modo = 'sem-ia';

  async inicializar() {
    // Tentar nativo primeiro
    if ('LanguageModel' in window) {
      const disponibilidade = await LanguageModel.availability({
        expectedInputs: [{ type: "text" }],
        expectedOutputs: [{ type: "text" }]
      });

      if (disponibilidade !== 'unavailable') {
        this.#session = await LanguageModel.create();
        this.#modo = 'nativo';
        return;
      }
    }

    // Tentar polyfill
    try {
      const config = await fetch('/api/ai-config').then(r => r.json());
      window.GEMINI_CONFIG = config;
      await import('prompt-api-polyfill');

      this.#session = await LanguageModel.create();
      this.#modo = 'polyfill';
    } catch (e) {
      this.#modo = 'sem-ia';
    }
  }

  async processar(texto) {
    switch (this.#modo) {
      case 'nativo':
      case 'polyfill':
        return await this.#session.prompt(texto);
      case 'sem-ia':
        return "IA não disponível neste browser.";
    }
  }

  get modo() { return this.#modo; }
}

Sem polyfill: fallback manual

Se preferir controle total:

async function criarMotorIA() {
  // 1. Prompt API nativa
  if ('LanguageModel' in window) {
    const status = await LanguageModel.availability();
    if (status === 'available') {
      const session = await LanguageModel.create();
      return {
        tipo: 'on-device',
        prompt: (texto) => session.prompt(texto),
        stream: (texto) => session.promptStreaming(texto),
        destroy: () => session.destroy()
      };
    }
  }

  // 2. Fallback para API cloud própria
  return {
    tipo: 'cloud',
    prompt: async (texto) => {
      const res = await fetch('/api/ia/prompt', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prompt: texto })
      });
      return (await res.json()).resposta;
    },
    stream: (texto) => {
      const eventSource = new EventSource(
        `/api/ia/stream?prompt=${encodeURIComponent(texto)}`
      );
      return new ReadableStream({
        start(controller) {
          eventSource.onmessage = (e) => {
            if (e.data === '[DONE]') {
              controller.close();
              eventSource.close();
            } else {
              controller.enqueue(e.data);
            }
          };
        }
      });
    },
    destroy: () => {}
  };
}

const motor = await criarMotorIA();
console.log(`Usando: ${motor.tipo}`);

Essa abordagem dá controle completo sobre o fallback. Mais código, mas nenhuma dependência extra.

Feature detection robusta

Ir além de checar se LanguageModel existe:

async function detectarCapacidadesIA() {
  const resultado = {
    apiDisponivel: false,
    modeloDisponivel: false,
    multimodal: false,
    structuredOutput: false,
    offline: !navigator.onLine
  };

  if (!('LanguageModel' in window)) return resultado;
  resultado.apiDisponivel = true;

  try {
    const status = await LanguageModel.availability({
      expectedInputs: [{ type: "text" }],
      expectedOutputs: [{ type: "text" }]
    });
    resultado.modeloDisponivel = status === 'available';
    if (status === 'unavailable') return resultado;
  } catch (e) {
    return resultado;
  }

  // Checar multimodal
  try {
    const imgStatus = await LanguageModel.availability({
      expectedInputs: [{ type: "text" }, { type: "image" }],
      expectedOutputs: [{ type: "text" }]
    });
    resultado.multimodal = imgStatus !== 'unavailable';
  } catch (e) {}

  resultado.structuredOutput = true; // desde Chrome 137
  return resultado;
}

Diferenças nativo vs. polyfill

Importante comunicar ao usuário:

AspectoNativo (on-device)Polyfill (cloud)
PrivacidadeDados nunca saemDados enviados ao provedor
Latência~100-500ms~200-1000ms (rede)
Offline
CustoGrátisPay-per-token
QualidadeGemini Nano (limitado)Modelo cloud (superior)
Download~4.27GB inicialNenhum
Context window~4K tokens32K-128K

Transparência com o usuário

As pessoas devem saber onde seus dados são processados:

function mostrarStatusIA(motor) {
  const badge = document.getElementById('ia-status');

  switch (motor.tipo) {
    case 'on-device':
      badge.textContent = '🟢 IA Local';
      badge.title = 'Processamento no seu dispositivo. Dados não saem do browser.';
      break;
    case 'cloud':
      badge.textContent = '☁️ IA Cloud';
      badge.title = 'Processamento via servidor. Dados enviados de forma segura.';
      break;
    case 'sem-ia':
      badge.textContent = '⚪ Sem IA';
      badge.title = 'IA não disponível neste browser.';
      break;
  }
}

Segurança da API key

Cuidado sério: o polyfill cloud expõe sua API key no frontend. Em produção:

// ❌ NUNCA em produção: API key exposta
window.GEMINI_CONFIG = { apiKey: "AIzaSy..." };

// ✅ Proxy via backend com rate limiting
const resposta = await fetch('/api/ai/prompt', {
  method: 'POST',
  body: JSON.stringify({ prompt: texto }),
  headers: { 'Authorization': `Bearer ${sessionToken}` }
});

Use Firebase AI Logic com App Check ou proxy próprio. API key no frontend é convite para abuso.

Tratando offline

O polyfill cloud não funciona sem rede. Trate isso:

window.addEventListener('offline', () => {
  if (motorAtual.tipo === 'cloud') {
    mostrarAviso('Offline. IA indisponível.');
    desabilitarBotoesIA();
  }
  // On-device continua funcionando
});

window.addEventListener('online', () => {
  if (motorAtual.tipo === 'cloud') {
    esconderAviso();
    habilitarBotoesIA();
  }
});

FAQ

O polyfill funciona em todos os browsers?

Sim, em qualquer browser com ES modules e dynamic imports. Firefox, Safari, Chrome antigo, Edge.

Preciso de API key para backend Transformers.js?

Não. Use "apiKey": "dummy". Processamento local. Mas um modelo ONNX precisa ser baixado (~100-500MB dependendo).

O polyfill suporta multimodal?

Sim, exceto: backend OpenAI não suporta áudio+imagem juntos, backend Transformers.js não suporta structured output. Backend Gemini suporta tudo.

Diferença de qualidade entre on-device e cloud?

Grande. Gemini Nano (~4B params) é otimizado para velocidade. Backends cloud usam modelos maiores com qualidade muito superior para tarefas complexas.

O polyfill simula downloadprogress?

Sim. Para backends cloud (sem download de modelo), emite dois eventos: loaded: 0 e loaded: 1. Sua aplicação não precisa de lógica especial.

Conclusão

O polyfill resolve o maior empecilho de adoção da Prompt API: rodar em todo lugar. Você codifica contra window.LanguageModel e funciona em Chrome (nativo), Edge (nativo), Firefox (polyfill), Safari (polyfill) — mesma interface, sem branch por browser.

A estratégia que eu recomendo: on-device quando disponível (privacidade, offline, sem custo), cloud via polyfill como fallback (compatibilidade, qualidade superior pra tarefas pesadas). Dá uma olhada nas limitações do Gemini Nano em benchmarks pra saber quando o modelo local basta e quando vale apelar pro cloud.


Próximo artigo: Performance e limitações do Gemini Nano em benchmarks reais

Referências