hashbrown

Getting Started

Guide

  1. 1. Basics of AI
  2. 2. System Instructions
  3. 3. Message History
  4. 4. Skillet Schema
  5. 5. Streaming
  6. 6. Tool Calling
  7. 7. Structured Output
  8. 8. Generative UI
  9. 9. JavaScript Runtime

Recipes

  1. Natural Language Forms
  2. UI Chatbot with Tools
  3. UI Kits
  4. Predictive Suggestions
  5. Remote MCP
  6. Threads
  7. Magic Text
  8. JSON Parser
  9. CopilotKit
  10. Local Models

Platforms

Local Models (Chrome + Edge)

Ship AI features that stay on the user's device and fall back to the network when needed.

What you'll learn:

  1. Enable the built-in models in Chrome or Edge (flags, hardware, languages)
  2. Wire experimental_local with cloud fallbacks in React
  3. Surface download/availability state in your UI
  4. Keep structured outputs working without tool calls

0. Why local models?

  • Privacy + offline: prompts never leave the device once the model is downloaded.
  • Zero per-request cost: no API key usage while the browser model is used.
  • Low latency: tokens stream locally; Hashbrown framing stays the same.

1. Prerequisites and flags (as of Dec 14, 2025)

Browser Languages System Requirements Flags to enable
Chrome - Gemini Nano en, es, ja >=22 GB disk; >4 GB VRAM or 16 GB RAM/4 cores optimization-guide-on-device-model, prompt-api-for-gemini-nano (or ...-multimodal-input)
Edge - Phi-4-mini en (others experimental) >=5.5 GB VRAM or strong CPU prompt-api-for-phi-mini

After toggling flags, restart the browser and run await LanguageModel.availability(); in DevTools to confirm readiness. Localhost does not require an origin trial.


2. Hashbrown building blocks

  • useStructuredCompletion from @hashbrownai/react to stream typed outputs.
  • experimental_local() from @hashbrownai/core/transport tries Chrome first, then Edge.
  • Pass a model array for fallback; Hashbrown advances on FEATURE_UNSUPPORTED or PLATFORM_UNSUPPORTED, but retries stay on the chosen adapter for generation errors.
  • Event hooks (availability, downloadRequired, downloadProgress) let you mirror browser download state in React state.

3. Quickstart: local-first structured completion

This component streams a two-day itinerary schema. It prefers the on-device model and falls back to gpt-5-mini if local is unavailable or unsupported.

import { useMemo, useState } from 'react';
import { s } from '@hashbrownai/core';
import { experimental_local } from '@hashbrownai/core/transport';
import { useStructuredCompletion } from '@hashbrownai/react';

const ItinerarySchema = s.object('2-day plan', {
  city: s.string('Destination city'),
  days: s.streaming.array(
    'List of days',
    s.object('Day', {
      title: s.streaming.string('Title'),
      highlights: s.streaming.array(
        'Top things to do',
        s.streaming.string('Activity'),
      ),
    }),
  ),
});

export function LocalItinerary() {
  const [city, setCity] = useState('Lisbon');
  const [availability, setAvailability] = useState<string | null>(null);
  const [downloadRequired, setDownloadRequired] = useState(false);
  const [downloadProgress, setDownloadProgress] = useState<number | null>(null);

  const input = useMemo(
    () => (city ? `Plan a concise two-day itinerary for ${city}.` : null),
    [city],
  );

  const { output, error, isSending, reload } = useStructuredCompletion({
    debugName: 'local-itinerary-react',
    input,
    system: 'Return a two-day itinerary as JSON that matches the schema.',
    schema: ItinerarySchema,
    model: [
      experimental_local({
        events: {
          availability: setAvailability,
          downloadRequired: () => setDownloadRequired(true),
          downloadProgress: setDownloadProgress,
        },
      }),
      'gpt-5-mini', // cloud fallback
    ],
  });

  return (
    <section style={{ display: 'grid', gap: 12, maxWidth: 520 }}>
      <div>
        <label style={{ display: 'block', fontWeight: 600 }}>Destination</label>
        <input
          value={city}
          onChange={(e) => setCity(e.target.value)}
          placeholder="Lisbon"
        />
      </div>

      <div style={{ fontSize: 14, color: '#555' }}>
        <div>Availability: {availability ?? 'checking...'}</div>
        {downloadRequired && (
          <div>Download required (triggered on first prompt)</div>
        )}
        {downloadProgress !== null && <div>Download: {downloadProgress}%</div>}
      </div>

      <button onClick={reload} disabled={isSending}>
        {isSending ? 'Generating...' : 'Generate locally'}
      </button>

      {output && (
        <pre style={{ background: '#0f172a', color: '#f8fafc', padding: 12 }}>
          {JSON.stringify(output, null, 2)}
        </pre>
      )}

      {error && <div style={{ color: 'crimson' }}>Error: {error.message}</div>}
    </section>
  );
}

How it works

  • experimental_local creates a stable transport that reuses a browser session; events keep your UI in sync with download state.
  • If tools are requested, local returns FEATURE_UNSUPPORTED and Hashbrown falls back to gpt-5-mini.
  • reload lets users retry after enabling flags or freeing disk space without remounting the component.

4. Troubleshooting (React focus)

  • PLATFORM_UNSUPPORTED: API missing, wrong browser channel, or flags off. Re-check the table above.
  • FEATURE_UNSUPPORTED: tools requested or schema unsupported; ensure a cloud fallback is present.
  • Slow first token: the model may still be downloading; keep surfacing downloadProgress.
  • Model evicted because of low disk: rerun with reload after the browser re-downloads the model.
Local Models (Chrome + Edge) 0. Why local models? 1. Prerequisites and flags (as of Dec 14, 2025) 2. Hashbrown building blocks 3. Quickstart: local-first structured completion 4. Troubleshooting (React focus)