@tryhamster/gerbil 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +10 -10
- package/dist/cli.mjs.map +1 -1
- package/dist/frameworks/express.d.mts +1 -1
- package/dist/frameworks/express.mjs +1 -1
- package/dist/frameworks/fastify.d.mts +1 -1
- package/dist/frameworks/fastify.mjs +1 -1
- package/dist/frameworks/hono.d.mts +1 -1
- package/dist/frameworks/hono.mjs +1 -1
- package/dist/frameworks/next.d.mts +3 -3
- package/dist/frameworks/next.mjs +1 -1
- package/dist/frameworks/react.d.mts +1 -1
- package/dist/frameworks/trpc.d.mts +1 -1
- package/dist/frameworks/trpc.mjs +1 -1
- package/dist/gerbil-BgppGzKa.mjs +4 -0
- package/dist/{gerbil-BPbJy0_f.mjs → gerbil-CZYYq4Sq.mjs} +4 -4
- package/dist/{gerbil-BPbJy0_f.mjs.map → gerbil-CZYYq4Sq.mjs.map} +1 -1
- package/dist/{gerbil-BetB5xb0.d.mts → gerbil-CvNqpVg0.d.mts} +3 -3
- package/dist/{gerbil-BetB5xb0.d.mts.map → gerbil-CvNqpVg0.d.mts.map} +1 -1
- package/dist/gpu/hooks.d.mts +2 -2
- package/dist/gpu/hooks.d.mts.map +1 -1
- package/dist/gpu/hooks.mjs +4 -5
- package/dist/gpu/hooks.mjs.map +1 -1
- package/dist/gpu/index.d.mts +1 -1
- package/dist/gpu/index.mjs +2 -3
- package/dist/{gpu-Dszu2rk-.mjs → gpu-Ch2VuLdN.mjs} +25 -4
- package/dist/gpu-Ch2VuLdN.mjs.map +1 -0
- package/dist/{index-Dgmb2kE3.d.mts → index-Dy_9rDd5.d.mts} +2 -2
- package/dist/{index-Dgmb2kE3.d.mts.map → index-Dy_9rDd5.d.mts.map} +1 -1
- package/dist/{index-DukkJRMj.d.mts → index-Nl40RdSs.d.mts} +1 -1
- package/dist/{index-DukkJRMj.d.mts.map → index-Nl40RdSs.d.mts.map} +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +8 -9
- package/dist/index.mjs.map +1 -1
- package/dist/{indexeddb-store-BWIMtxxH.mjs → indexeddb-store-BEylGAex.mjs} +2 -2
- package/dist/{indexeddb-store-BWIMtxxH.mjs.map → indexeddb-store-BEylGAex.mjs.map} +1 -1
- package/dist/indexeddb-store-ClSHQxwz.mjs +4 -0
- package/dist/integrations/ai-sdk.d.mts +1 -1
- package/dist/integrations/ai-sdk.mjs +1 -1
- package/dist/integrations/langchain.d.mts +1 -1
- package/dist/integrations/langchain.mjs +1 -1
- package/dist/integrations/llamaindex.d.mts +1 -1
- package/dist/integrations/llamaindex.mjs +1 -1
- package/dist/integrations/mcp-client.mjs +1 -1
- package/dist/integrations/mcp.d.mts +3 -3
- package/dist/integrations/mcp.mjs +4 -4
- package/dist/{mcp-C8lTkR3D.mjs → mcp-DUgk28Kp.mjs} +3 -3
- package/dist/{mcp-C8lTkR3D.mjs.map → mcp-DUgk28Kp.mjs.map} +1 -1
- package/dist/memory/index.d.mts +2 -2
- package/dist/memory/index.mjs +4 -4
- package/dist/memory-CGRo6zY5.mjs +4 -0
- package/dist/{memory-DVN0MnIG.mjs → memory-Deo99oKh.mjs} +2 -2
- package/dist/{memory-DVN0MnIG.mjs.map → memory-Deo99oKh.mjs.map} +1 -1
- package/dist/{memory-Dj0J1v88.mjs → memory-wgo2ZK3M.mjs} +2 -2
- package/dist/{memory-Dj0J1v88.mjs.map → memory-wgo2ZK3M.mjs.map} +1 -1
- package/dist/moonshine-stt-DFIP4tIb.mjs +4 -0
- package/dist/{moonshine-stt-DNVw8v_Y.mjs → moonshine-stt-DR0-JbVW.mjs} +1 -1
- package/dist/{moonshine-stt-DNVw8v_Y.mjs.map → moonshine-stt-DR0-JbVW.mjs.map} +1 -1
- package/dist/{one-liner-VcUGmyTZ.mjs → one-liner-sTDG9Hum.mjs} +2 -2
- package/dist/{one-liner-VcUGmyTZ.mjs.map → one-liner-sTDG9Hum.mjs.map} +1 -1
- package/dist/repl-njlFQs4-.mjs +9 -0
- package/dist/skills/index.d.mts +14 -14
- package/dist/skills/index.d.mts.map +1 -1
- package/dist/skills/index.mjs +3 -3
- package/dist/{skills-B6Cor5wy.mjs → skills-CEUuOUlm.mjs} +2 -2
- package/dist/{skills-B6Cor5wy.mjs.map → skills-CEUuOUlm.mjs.map} +1 -1
- package/dist/{tools-DQ1mPUw5.mjs → tools-DbbNCJ5y.mjs} +1 -1
- package/dist/{tools-DQ1mPUw5.mjs.map → tools-DbbNCJ5y.mjs.map} +1 -1
- package/dist/{types-LlyYILII.d.mts → types-Bxwe_uS7.d.mts} +1 -1
- package/dist/{types-LlyYILII.d.mts.map → types-Bxwe_uS7.d.mts.map} +1 -1
- package/dist/{types-DQBe2lFo.d.mts → types-D7kn-0i2.d.mts} +1 -1
- package/dist/{types-DQBe2lFo.d.mts.map → types-D7kn-0i2.d.mts.map} +1 -1
- package/dist/{vector-B0panuy6.mjs → vector-C1U9vVVS.mjs} +1 -1
- package/dist/{vector-B0panuy6.mjs.map → vector-C1U9vVVS.mjs.map} +1 -1
- package/package.json +1 -1
- package/dist/defaults-9komdrbY.mjs +0 -24
- package/dist/defaults-9komdrbY.mjs.map +0 -1
- package/dist/gerbil-BoHXwcdY.mjs +0 -4
- package/dist/gpu-Dszu2rk-.mjs.map +0 -1
- package/dist/indexeddb-store-ClH12Xnl.mjs +0 -4
- package/dist/memory-D1P7Tmda.mjs +0 -4
- package/dist/moonshine-stt-rXESw8t8.mjs +0 -4
- package/dist/repl-WUbQ1UgU.mjs +0 -9
- /package/dist/{auto-update-BVaLXcDE.mjs → auto-update-DNbr3GLz.mjs} +0 -0
- /package/dist/{microphone-Bqmoz9_K.mjs → microphone-sj_bnRm-.mjs} +0 -0
package/dist/gpu/hooks.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.mjs","names":["created: SharedEngineEntry","state: GerbilGateState","turns: ChatMessage[]","stream: MediaStream"],"sources":["../../src/browser/use-engine.ts","../../src/browser/gerbil-gate.tsx","../../src/browser/use-agent.ts","../../src/browser/use-autocomplete.ts","../../src/browser/use-memory.ts","../../src/browser/use-modalities.ts","../../src/browser/use-stt.ts","../../src/browser/use-tts.ts","../../src/browser/use-voice-chat.ts"],"sourcesContent":["/**\n * React hook for native WebGPU inference in the browser.\n *\n * Uses gerbil's WebGPUEngine directly on the main thread — no web worker,\n * no ONNX Runtime, no transformers.js. Pure WGSL compute shaders.\n *\n * Handles the full engine lifecycle for you:\n * - loads the model (lazily or on mount),\n * - hot-swaps when you change `model`/`dtype`/`enableVision`/`embedding`,\n * - SHARES one engine across every component that asks for the same config\n * (reference-counted) so you never upload the same weights to the GPU twice,\n * - disposes when the last consumer unmounts.\n *\n * @example\n * ```tsx\n * import { useEngine } from \"@tryhamster/gerbil/browser\";\n *\n * function App() {\n * const { complete, completion, isLoading, isGenerating, tps } = useEngine({\n * model: \"mlx-community/Qwen3.5-0.8B-4bit\",\n * autoLoad: true,\n * });\n *\n * if (isLoading) return <div>Loading model...</div>;\n * return (\n * <div>\n * <button onClick={() => complete(\"What is 2+2?\")}>Generate</button>\n * <p>{completion}</p>\n * {isGenerating && <span>{tps?.toFixed(1)} tok/s</span>}\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { resolveDefaultRepo } from \"../gpu/defaults.js\";\n\n// Lazy-loaded WebGPUEngine type (imported dynamically to avoid bundling GPU code\n// into the main browser bundle unless this hook is actually used)\ntype WebGPUEngineType = import(\"../gpu/index.js\").WebGPUEngine;\ntype AgentTool = import(\"../gpu/index.js\").AgentTool;\ntype AgentStep = import(\"../gpu/index.js\").AgentStep;\n\n/** A single conversation turn (matches the engine's chat message shape). */\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\nexport type EngineErrorKind =\n | \"no-webgpu\"\n | \"no-adapter\"\n | \"device-lost\"\n | \"oom\"\n | \"network\"\n | \"timeout\"\n | \"unknown\";\n\nexport interface UseEngineOptions {\n /**\n * HuggingFace repo ID (e.g., \"mlx-community/Qwen3.5-0.8B-4bit\"). Optional —\n * defaults to the built-in model for the requested capability (text, vision,\n * or embeddings).\n */\n model?: string;\n /** Max sequence length (default: auto — 2048 on mobile, 4096 on desktop). */\n maxSeqLen?: number;\n /**\n * Weight dtype. Default \"auto\": int4 on mobile (fits device memory), the\n * repo's native precision on desktop. Already-quantized repos stay q4.\n */\n dtype?: \"auto\" | \"f32\" | \"q4\";\n /** Auto-load the model on mount (default: false). */\n autoLoad?: boolean;\n /** Build the vision encoder so `describeImage()` works (vision checkpoints only). */\n enableVision?: boolean;\n /** Load as an embedding model so `embed()`/`similarity()` work. */\n embedding?: boolean;\n /** Loading timeout in ms (default: 300000 = 5 minutes). */\n loadingTimeout?: number;\n /** Called when the engine is ready. */\n onReady?: () => void;\n /** Called on error, with a coarse error kind for UI messaging. */\n onError?: (error: Error, kind: EngineErrorKind) => void;\n}\n\nexport interface CompleteOptions {\n /** Max tokens to generate (default: 256). */\n maxTokens?: number;\n /** Temperature (0 = greedy, default: 0.7). */\n temperature?: number;\n /** System prompt. */\n system?: string;\n /** Stop sequences. */\n stopSequences?: string[];\n}\n\nexport interface DescribeImageOptions extends CompleteOptions {\n /** Override the maximum tokens generated for the description. */\n maxTokens?: number;\n}\n\n/** Validator for {@link UseEngineReturn.generateObject}: a schema object or predicate. */\nexport type ObjectValidator<T = unknown> =\n | { required?: string[]; properties?: Record<string, unknown>; [key: string]: unknown }\n | ((o: T) => boolean);\n\nexport interface GenerateObjectOptions extends CompleteOptions {\n /**\n * Validation target. Either a predicate `(o) => boolean` or a minimal\n * JSON-schema-ish object with `required` keys. Omit to require valid JSON only.\n */\n schema?: ObjectValidator;\n /** Max retries after the first attempt (default: 4). */\n maxRetries?: number;\n}\n\nexport interface UseEngineReturn {\n /** Generate a completion for a prompt. Returns the full text. */\n complete: (prompt: string | ChatMessage[], options?: CompleteOptions) => Promise<string>;\n /** Inline autocomplete: continue `prefix` with a brief single-line completion. */\n autocomplete: (\n prefix: string,\n opts?: { maxTokens?: number; temperature?: number; stop?: string[]; singleLine?: boolean },\n ) => Promise<string>;\n /** Rewrite `text` in a target tone (or with free-form instructions). */\n rewrite: (\n text: string,\n opts?: { tone?: string; instructions?: string; maxTokens?: number; temperature?: number },\n ) => Promise<string>;\n /** Agentic tool-calling loop: generate → run tools → repeat until an answer. */\n generateWithTools: (\n prompt: string | ChatMessage[],\n opts: {\n tools: AgentTool[];\n maxSteps?: number;\n onStep?: (step: AgentStep) => void;\n maxTokens?: number;\n sampling?: { temperature?: number; topK?: number; topP?: number };\n },\n ) => Promise<{ text: string; steps: AgentStep[] }>;\n /**\n * Describe an image (image-in → text-out). Requires `enableVision: true`.\n * `image` may be a data/http(s) URL or pre-decoded RGB pixels.\n */\n describeImage: (\n image:\n | string\n | { pixels: Uint8ClampedArray | Uint8Array | Float32Array; width: number; height: number },\n prompt?: string,\n options?: DescribeImageOptions,\n ) => Promise<string>;\n /**\n * Generate a structured object: generate → parse JSON → validate → retry\n * until valid. Returns the parsed object plus the attempt count.\n */\n generateObject: <T = unknown>(\n prompt: string,\n options?: GenerateObjectOptions,\n ) => Promise<{ object: T; attempts: number }>;\n /** Embed text into an L2-normalized vector. Requires `embedding: true`. */\n embed: (text: string, options?: { taskType?: \"query\" | \"document\" }) => Promise<Float32Array>;\n /** Cosine similarity between two texts. Requires `embedding: true`. */\n similarity: (a: string, b: string) => Promise<number>;\n /** Current completion text (streams in token by token). */\n completion: string;\n /** Whether the model is loading. */\n isLoading: boolean;\n /** Loading progress info. */\n loadingProgress: { status: string; progress?: number } | null;\n /** Whether generation is in progress. */\n isGenerating: boolean;\n /** Current tokens per second. */\n tps: number | null;\n /** Attempts taken by the last `generateObject` call (0 until one runs). */\n attempts: number;\n /** Current error message. */\n error: string | null;\n /** Coarse error kind, for tailored UI messaging. */\n errorKind: EngineErrorKind | null;\n /** Whether the engine is ready. */\n isReady: boolean;\n /** Manually load the model. */\n load: () => Promise<void>;\n /** Stop current generation. */\n stop: () => void;\n /** Release this consumer's hold on the engine (disposes when no holders remain). */\n dispose: () => void;\n}\n\n// ── Error classification ────────────────────────────────────────────────\n\nfunction classifyError(err: Error): EngineErrorKind {\n const msg = err.message.toLowerCase();\n if (msg.includes(\"webgpu is not available\") || msg.includes(\"not supported\")) return \"no-webgpu\";\n if (msg.includes(\"no webgpu adapter\") || msg.includes(\"no gpu adapter\")) return \"no-adapter\";\n if (msg.includes(\"device lost\") || msg.includes(\"device was destroyed\")) return \"device-lost\";\n if (\n msg.includes(\"out of memory\") ||\n msg.includes(\"allocation failed\") ||\n msg.includes(\"buffer size\")\n )\n return \"oom\";\n if (msg.includes(\"fetch\") || msg.includes(\"network\") || msg.includes(\"cors\")) return \"network\";\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\")) return \"timeout\";\n return \"unknown\";\n}\n\nfunction getErrorGuidance(kind: EngineErrorKind): string {\n switch (kind) {\n case \"no-webgpu\":\n return \"WebGPU requires Safari 26+ (iOS 26+), Chrome 113+, or Firefox 141+.\";\n case \"no-adapter\":\n return \"No GPU found. Try closing other browser tabs that might be using the GPU.\";\n case \"device-lost\":\n return \"GPU device was lost (tab may have been backgrounded). Please reload.\";\n case \"oom\":\n return \"Not enough GPU memory. Try a smaller model or close other tabs.\";\n case \"network\":\n return \"Failed to download model. Check your internet connection.\";\n case \"timeout\":\n return \"Model loading timed out. Check your connection or try a smaller model.\";\n default:\n return \"\";\n }\n}\n\nfunction getDefaultMaxSeqLen(): number {\n if (typeof navigator === \"undefined\") return 4096;\n const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n return isMobile ? 2048 : 4096;\n}\n\n// ── Module-level shared engine registry ─────────────────────────────────\n//\n// Components requesting the SAME (model|dtype|vision|embedding|maxSeqLen) share\n// ONE WebGPUEngine, reference-counted. This is what stops the page-narrator, the\n// highlight-to-explain feature, and the playground from each uploading the same\n// Qwen weights to the GPU. The engine is created once, awaited by all holders,\n// and destroyed when the last holder releases it.\n\ninterface SharedEngineEntry {\n promise: Promise<WebGPUEngineType>;\n engine: WebGPUEngineType | null;\n refs: number;\n /** Pending deferred-disposal timer (set when refs hit 0, cleared on re-acquire). */\n disposeTimer: ReturnType<typeof setTimeout> | null;\n}\n\nconst SHARED_ENGINES = new Map<string, SharedEngineEntry>();\n\n// Grace period before a released (refs===0) engine is actually destroyed. Lets a\n// transient unmount→remount — Next.js dynamic() + hydration, route changes, tab\n// switches — reuse the warm engine instead of destroying and re-creating it\n// (which uploads the weights to the GPU a second time). Re-acquiring within the\n// window cancels the disposal.\nconst ENGINE_DISPOSE_GRACE_MS = 30_000;\n\n// After a failed load, block an immediate re-load of the same key for this long.\n// Swallows the synchronous effect re-fire that would otherwise loop, while still\n// letting a user retry a few seconds later.\nconst RETRY_COOLDOWN_MS = 3000;\n\nfunction acquireSharedEngine(\n key: string,\n factory: () => Promise<WebGPUEngineType>,\n): Promise<WebGPUEngineType> {\n let entry = SHARED_ENGINES.get(key);\n if (!entry) {\n const created: SharedEngineEntry = {\n promise: factory(),\n engine: null,\n refs: 0,\n disposeTimer: null,\n };\n created.promise\n .then((eng) => {\n created.engine = eng;\n })\n .catch(() => {\n // Failed load: drop the entry so a later mount can retry from scratch.\n SHARED_ENGINES.delete(key);\n });\n SHARED_ENGINES.set(key, created);\n entry = created;\n }\n // Re-acquired within the grace window → cancel any pending disposal.\n if (entry.disposeTimer) {\n clearTimeout(entry.disposeTimer);\n entry.disposeTimer = null;\n }\n entry.refs += 1;\n return entry.promise;\n}\n\nfunction releaseSharedEngine(key: string): void {\n const entry = SHARED_ENGINES.get(key);\n if (!entry) return;\n entry.refs -= 1;\n if (entry.refs > 0 || entry.disposeTimer) return;\n // Defer destruction so a quick remount can reuse the warm engine. Only destroy\n // if still unreferenced when the timer fires.\n entry.disposeTimer = setTimeout(() => {\n entry.disposeTimer = null;\n if (entry.refs > 0) return;\n SHARED_ENGINES.delete(key);\n entry.promise\n .then((eng) => eng.destroy())\n .catch(() => {\n /* load already failed and was cleaned up */\n });\n }, ENGINE_DISPOSE_GRACE_MS);\n}\n\n/** Decode an image URL / data URL into RGB pixels via an offscreen canvas. */\nasync function decodeImage(\n src: string,\n): Promise<{ pixels: Uint8ClampedArray; width: number; height: number }> {\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject(new Error(\"Failed to load image.\"));\n img.src = src;\n });\n // Cap the longest side: ViT attention memory scales with patch-count², so a\n // full-resolution photo blows past the engine's maxVisionPatches. ≤448px keeps\n // it coherent at ~7× fewer patches.\n const MAX_DIM = 448;\n const scale = Math.min(1, MAX_DIM / Math.max(img.naturalWidth, img.naturalHeight));\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(img.naturalWidth * scale));\n canvas.height = Math.max(1, Math.round(img.naturalHeight * scale));\n const cctx = canvas.getContext(\"2d\");\n if (!cctx) throw new Error(\"Could not get 2D canvas context for image decode.\");\n cctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n const data = cctx.getImageData(0, 0, canvas.width, canvas.height);\n const rgba = data.data;\n const rgb = new Uint8ClampedArray(canvas.width * canvas.height * 3);\n for (let i = 0, j = 0; i < rgba.length; i += 4, j += 3) {\n rgb[j] = rgba[i];\n rgb[j + 1] = rgba[i + 1];\n rgb[j + 2] = rgba[i + 2];\n }\n return { pixels: rgb, width: canvas.width, height: canvas.height };\n}\n\nexport function useEngine(options: UseEngineOptions = {}): UseEngineReturn {\n const {\n model: modelOption,\n maxSeqLen,\n dtype = \"auto\",\n autoLoad = false,\n enableVision = false,\n embedding = false,\n loadingTimeout = 300_000,\n onReady,\n onError,\n } = options;\n // Resolve the default model for the requested capability when none is given.\n const model = resolveDefaultRepo({ repo: modelOption, embedding, enableVision });\n\n const engineRef = useRef<WebGPUEngineType | null>(null);\n const stoppedRef = useRef(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // The shared-registry key this hook instance currently holds a ref on.\n const heldKeyRef = useRef<string | null>(null);\n // A key whose load failed, with the timestamp of the failure. Consumers call\n // load() from an effect gated on `!isReady && !isLoading`, so a single failed\n // load would otherwise re-fire instantly forever (create → fail → create → …).\n // We block re-loading the SAME key only within a short cooldown — long enough\n // to swallow that instant effect re-fire, short enough that a user retrying\n // (selecting text again, clicking generate) a moment later actually retries.\n const failedKeyRef = useRef<{ key: string; at: number } | null>(null);\n\n const [isLoading, setIsLoading] = useState(false);\n const [loadingProgress, setLoadingProgress] = useState<{\n status: string;\n progress?: number;\n } | null>(null);\n const [isGenerating, setIsGenerating] = useState(false);\n const [isReady, setIsReady] = useState(false);\n const [completion, setCompletion] = useState(\"\");\n const [tps, setTps] = useState<number | null>(null);\n const [attempts, setAttempts] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [errorKind, setErrorKind] = useState<EngineErrorKind | null>(null);\n\n const modelKey = `${model}|${dtype}|${enableVision}|${embedding}|${maxSeqLen ?? \"auto\"}`;\n\n const fail = useCallback(\n (e: unknown) => {\n const err = e instanceof Error ? e : new Error(String(e));\n const kind = classifyError(err);\n const guidance = getErrorGuidance(kind);\n setError(guidance ? `${err.message} ${guidance}` : err.message);\n setErrorKind(kind);\n setIsLoading(false);\n setLoadingProgress(null);\n onError?.(err, kind);\n },\n [onError],\n );\n\n const load = useCallback(async () => {\n const failed = failedKeyRef.current;\n const inCooldown = failed?.key === modelKey && Date.now() - failed.at < RETRY_COOLDOWN_MS;\n if (engineRef.current || heldKeyRef.current === modelKey || inCooldown) return;\n\n // Pre-check WebGPU before the expensive dynamic import.\n if (typeof navigator === \"undefined\" || !(\"gpu\" in navigator)) {\n failedKeyRef.current = { key: modelKey, at: Date.now() };\n fail(new Error(\"WebGPU is not available in this browser.\"));\n return;\n }\n\n setIsLoading(true);\n setError(null);\n setErrorKind(null);\n setLoadingProgress({ status: \"Initializing WebGPU engine...\" });\n\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutRef.current = setTimeout(\n () => reject(new Error(\"Model loading timed out. The download may be too slow.\")),\n loadingTimeout,\n );\n });\n\n const key = modelKey;\n heldKeyRef.current = key;\n\n const factory = async () => {\n const { WebGPUEngine } = await import(\"../gpu/index.js\");\n return WebGPUEngine.create({\n repo: model,\n maxSeqLen: maxSeqLen ?? getDefaultMaxSeqLen(),\n dtype,\n enableVision,\n embedding,\n onProgress: (loaded: number, total: number, message: string) => {\n setLoadingProgress({\n status: message,\n progress: total > 0 ? Math.round((loaded / total) * 100) : undefined,\n });\n },\n });\n };\n\n try {\n const engine = await Promise.race([acquireSharedEngine(key, factory), timeoutPromise]);\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n // A swap may have superseded this load while it was in flight.\n if (heldKeyRef.current !== key) {\n releaseSharedEngine(key);\n return;\n }\n engineRef.current = engine;\n failedKeyRef.current = null;\n if (typeof window !== \"undefined\")\n (window as { __gerbilEngine?: unknown }).__gerbilEngine = engine;\n setIsReady(true);\n setIsLoading(false);\n setLoadingProgress(null);\n onReady?.();\n } catch (e) {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n // Record the failure (with timestamp) so the consumer's load effect can't\n // instantly re-fire into a tight loop, but a user retry after the cooldown\n // still works. Release the hold so a DIFFERENT key (config change) can load.\n failedKeyRef.current = { key, at: Date.now() };\n if (heldKeyRef.current === key) heldKeyRef.current = null;\n releaseSharedEngine(key);\n fail(e);\n }\n }, [modelKey, model, maxSeqLen, dtype, enableVision, embedding, loadingTimeout, onReady, fail]);\n\n const stop = useCallback(() => {\n stoppedRef.current = true;\n }, []);\n\n const dispose = useCallback(() => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n engineRef.current = null;\n failedKeyRef.current = null;\n setIsReady(false);\n if (heldKeyRef.current) {\n releaseSharedEngine(heldKeyRef.current);\n heldKeyRef.current = null;\n }\n }, []);\n\n const complete = useCallback(\n async (prompt: string | ChatMessage[], opts: CompleteOptions = {}): Promise<string> => {\n const engine = engineRef.current;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n setIsGenerating(true);\n setCompletion(\"\");\n setTps(null);\n stoppedRef.current = false;\n let fullText = \"\";\n try {\n const result = await engine.generate(prompt, {\n maxTokens: opts.maxTokens ?? 256,\n sampling: { temperature: opts.temperature ?? 0.7 },\n systemPrompt: opts.system,\n stopSequences: opts.stopSequences,\n onToken: (token, meta) => {\n if (stoppedRef.current) return;\n fullText += token;\n setCompletion(fullText);\n // Live decode-only tok/s, updated each token during generation.\n if (meta) setTps(meta.tps);\n },\n });\n // Final accurate value (decode-only) once generation completes.\n setTps(result.tokensPerSecond);\n setIsGenerating(false);\n return result.text;\n } catch (e) {\n setIsGenerating(false);\n fail(e);\n return fullText;\n }\n },\n [fail],\n );\n\n const autocomplete = useCallback(\n async (\n prefix: string,\n opts?: { maxTokens?: number; temperature?: number; stop?: string[]; singleLine?: boolean },\n ): Promise<string> => {\n const engine = engineRef.current;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n return engine.autocomplete(prefix, opts);\n },\n [],\n );\n\n const rewrite = useCallback(\n async (\n text: string,\n opts?: { tone?: string; instructions?: string; maxTokens?: number; temperature?: number },\n ): Promise<string> => {\n const engine = engineRef.current;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n return engine.rewrite(text, opts);\n },\n [],\n );\n\n const generateWithTools = useCallback(\n (\n prompt: string | ChatMessage[],\n opts: {\n tools: AgentTool[];\n maxSteps?: number;\n onStep?: (step: AgentStep) => void;\n maxTokens?: number;\n sampling?: { temperature?: number; topK?: number; topP?: number };\n },\n ) => {\n const engine = engineRef.current;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n return engine.generateWithTools(prompt, opts);\n },\n [],\n );\n\n const generateObject = useCallback(\n async <T = unknown>(\n prompt: string,\n opts: GenerateObjectOptions = {},\n ): Promise<{ object: T; attempts: number }> => {\n const engine = engineRef.current as\n | (WebGPUEngineType & {\n generateObject: (\n p: string,\n o: unknown,\n ) => Promise<{ object: unknown; text: string; attempts: number }>;\n })\n | null;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n setIsGenerating(true);\n setCompletion(\"\");\n setTps(null);\n setAttempts(0);\n stoppedRef.current = false;\n try {\n const result = await engine.generateObject(prompt, {\n schema: opts.schema,\n maxRetries: opts.maxRetries,\n maxTokens: opts.maxTokens ?? 256,\n sampling: { temperature: opts.temperature ?? 0.7 },\n systemPrompt: opts.system,\n stopSequences: opts.stopSequences,\n });\n setCompletion(result.text);\n setAttempts(result.attempts);\n setIsGenerating(false);\n return { object: result.object as T, attempts: result.attempts };\n } catch (e) {\n setIsGenerating(false);\n fail(e);\n throw e instanceof Error ? e : new Error(String(e));\n }\n },\n [fail],\n );\n\n const describeImage = useCallback(\n async (\n image:\n | string\n | { pixels: Uint8ClampedArray | Uint8Array | Float32Array; width: number; height: number },\n prompt = \"Describe this image.\",\n opts: DescribeImageOptions = {},\n ): Promise<string> => {\n const engine = engineRef.current as\n | (WebGPUEngineType & {\n hasVision: boolean;\n describeImage: (\n img: unknown,\n p: string,\n o: unknown,\n ) => Promise<{ text: string; tokensPerSecond: number }>;\n })\n | null;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n if (!engine.hasVision) throw new Error(\"Engine was not created with enableVision: true.\");\n setIsGenerating(true);\n setCompletion(\"\");\n setTps(null);\n stoppedRef.current = false;\n const decoded = typeof image === \"string\" ? await decodeImage(image) : image;\n let fullText = \"\";\n try {\n const result = await engine.describeImage(decoded, prompt, {\n maxTokens: opts.maxTokens ?? 150,\n sampling: { temperature: opts.temperature ?? 0.7 },\n systemPrompt: opts.system,\n stopSequences: opts.stopSequences,\n onToken: (token, meta) => {\n if (stoppedRef.current) return;\n fullText += token;\n setCompletion(fullText);\n // Live decode-only tok/s, updated each token during generation.\n if (meta) setTps(meta.tps);\n },\n });\n // Final accurate value (decode-only) once generation completes.\n setTps(result.tokensPerSecond);\n setIsGenerating(false);\n return result.text;\n } catch (e) {\n setIsGenerating(false);\n fail(e);\n return fullText;\n }\n },\n [fail],\n );\n\n const embed = useCallback(\n async (text: string, opts: { taskType?: \"query\" | \"document\" } = {}): Promise<Float32Array> => {\n const engine = engineRef.current as\n | (WebGPUEngineType & {\n isEmbedding: boolean;\n embed: (t: string, o: unknown) => Promise<Float32Array>;\n })\n | null;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n if (!engine.isEmbedding) throw new Error(\"Engine was not created with embedding: true.\");\n return engine.embed(text, { taskType: opts.taskType ?? \"query\" });\n },\n [],\n );\n\n const similarity = useCallback(\n async (a: string, b: string): Promise<number> => {\n const [va, vb] = await Promise.all([\n embed(a, { taskType: \"query\" }),\n embed(b, { taskType: \"document\" }),\n ]);\n let dot = 0;\n const n = Math.min(va.length, vb.length);\n for (let i = 0; i < n; i++) dot += va[i] * vb[i];\n return dot;\n },\n [embed],\n );\n\n // Initial autoLoad + teardown on unmount.\n useEffect(() => {\n if (autoLoad) load();\n return () => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n engineRef.current = null;\n if (heldKeyRef.current) {\n releaseSharedEngine(heldKeyRef.current);\n heldKeyRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Hot-swap when the config changes. Fires whenever an engine is already held\n // (whether loaded eagerly via autoLoad or lazily via a manual load()) and the\n // config differs — release the old engine and load the new one. If nothing is\n // loaded yet (heldKeyRef === null), do nothing, so lazy consumers stay lazy.\n useEffect(() => {\n if (heldKeyRef.current === modelKey) return; // current engine matches\n // This effect runs ONLY when modelKey changes, so reaching here means the\n // config genuinely changed — a previously-failed key no longer applies.\n failedKeyRef.current = null;\n if (heldKeyRef.current === null) return; // nothing loaded; consumer loads if it wants\n engineRef.current = null;\n releaseSharedEngine(heldKeyRef.current);\n heldKeyRef.current = null;\n setIsReady(false);\n setCompletion(\"\");\n setTps(null);\n setError(null);\n setErrorKind(null);\n load();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [modelKey]);\n\n return {\n complete,\n autocomplete,\n rewrite,\n generateWithTools,\n generateObject,\n describeImage,\n embed,\n similarity,\n completion,\n isLoading,\n loadingProgress,\n isGenerating,\n tps,\n attempts,\n error,\n errorKind,\n isReady,\n load,\n stop,\n dispose,\n };\n}\n","/**\n * Gate your app behind the model download.\n *\n * Wrap your app (or a section) in `<GerbilGate>` to show a splash / loading\n * screen while the engine downloads + initializes, then render children once it\n * is ready — so by the time your app renders, you KNOW the engine is live.\n *\n * Because the gate stays mounted (typically at the root), it also keeps the\n * shared engine warm for the lifetime of the app: the reference never drops to\n * zero, so client-side navigation never re-uploads the weights to the GPU or\n * hits the 30s teardown window. Fully optional — the inline `await load()` /\n * `autoLoad` pattern still works.\n *\n * @example\n * ```tsx\n * import { GerbilGate } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * <GerbilGate\n * model=\"mlx-community/Qwen3.5-0.8B-4bit\"\n * fallback={({ progress, status }) => (\n * <Splash>{status} {progress != null && `${progress}%`}</Splash>\n * )}\n * errorFallback={({ error }) => <ErrorScreen message={error} />}\n * >\n * <App />\n * </GerbilGate>\n * ```\n */\n\nimport type { ReactNode } from \"react\";\nimport type { EngineErrorKind } from \"./use-engine.js\";\nimport { useEngine } from \"./use-engine.js\";\n\n/** Load state passed to the gate's `fallback` / `errorFallback` render functions. */\nexport interface GerbilGateState {\n isLoading: boolean;\n /** 0–100 download progress, or `null` if not yet known. */\n progress: number | null;\n /** Human-readable status (e.g. \"Downloading model…\"), or `null`. */\n status: string | null;\n /** Error message if the engine failed to load, or `null`. */\n error: string | null;\n /** Coarse error kind for UI branching (e.g. \"no-webgpu\"), or `null`. */\n errorKind: EngineErrorKind | null;\n}\n\ntype GateSlot = ReactNode | ((state: GerbilGateState) => ReactNode);\n\nexport interface GerbilGateProps {\n /** HuggingFace repo id to preload (defaults to the built-in text model). */\n model?: string;\n /** Rendered once the engine is ready. */\n children: ReactNode;\n /** Splash / loading UI shown until ready. A node, or a render fn given load state. */\n fallback?: GateSlot;\n /** Rendered if the engine fails to load. Falls back to `fallback` when omitted. */\n errorFallback?: GateSlot;\n /** Load as an embedding model (`embed()` / `similarity()`). */\n embedding?: boolean;\n /** Build the vision encoder so `describeImage()` works (vision checkpoints only). */\n enableVision?: boolean;\n /** Weight dtype (default \"auto\": int4 on mobile, native on desktop). */\n dtype?: \"auto\" | \"f32\" | \"q4\";\n /** Max sequence length (default: auto). */\n maxSeqLen?: number;\n}\n\nfunction renderSlot(slot: GateSlot, state: GerbilGateState): ReactNode {\n return typeof slot === \"function\" ? slot(state) : slot;\n}\n\nexport function GerbilGate({\n model,\n children,\n fallback = null,\n errorFallback,\n embedding,\n enableVision,\n dtype,\n maxSeqLen,\n}: GerbilGateProps) {\n const engine = useEngine({\n model,\n embedding,\n enableVision,\n dtype,\n maxSeqLen,\n autoLoad: true,\n });\n\n const state: GerbilGateState = {\n isLoading: engine.isLoading,\n progress: engine.loadingProgress?.progress ?? null,\n status: engine.loadingProgress?.status ?? null,\n error: engine.error,\n errorKind: engine.errorKind,\n };\n\n if (engine.error) {\n return <>{renderSlot(errorFallback ?? fallback, state)}</>;\n }\n if (!engine.isReady) {\n return <>{renderSlot(fallback, state)}</>;\n }\n return <>{children}</>;\n}\n","/**\n * React hook for agentic tool-calling in the browser.\n *\n * Owns the agent loop state (running flag, step trace, final answer) on top of\n * `useEngine().generateWithTools`. You supply the model + tools; the hook runs the\n * generate → call-tool → feed-result loop and exposes the steps for trace UIs.\n *\n * @example\n * ```tsx\n * import { useAgent } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { run, steps, answer, isRunning } = useAgent({\n * model: \"mlx-community/Qwen3.5-0.8B-4bit\",\n * tools: [weatherTool],\n * });\n * await run(\"What's the weather in Paris?\");\n * ```\n */\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { AgentStep, AgentTool } from \"../gpu/index.js\";\nimport { useEngine } from \"./use-engine.js\";\n\nexport interface UseAgentOptions {\n /** Model repo id to run the agent with. */\n model: string;\n /** Tools the agent may call. */\n tools: AgentTool[];\n /** Max generate → tool → result rounds before giving up (default 5). */\n maxSteps?: number;\n /** Load the model on mount (default false — loads on first run). */\n autoLoad?: boolean;\n}\n\nexport interface UseAgentReturn {\n /** Run the agent on a prompt; resolves to the final answer text. */\n run: (prompt: string) => Promise<string>;\n /** The step trace of the most recent run (tool calls, results, final answer). */\n steps: AgentStep[];\n /** Final answer text of the most recent run. */\n answer: string;\n /** True while the agent loop is running. */\n isRunning: boolean;\n /** True once the model is loaded. */\n isReady: boolean;\n /** Load the model explicitly. */\n load: () => Promise<void>;\n /** Clear the steps + answer. */\n reset: () => void;\n /** Load/run error, if any. */\n error: string | null;\n}\n\nexport function useAgent(options: UseAgentOptions): UseAgentReturn {\n const { model, tools, maxSteps = 5, autoLoad = false } = options;\n const engine = useEngine({ model, autoLoad });\n const [steps, setSteps] = useState<AgentStep[]>([]);\n const [answer, setAnswer] = useState(\"\");\n const [isRunning, setIsRunning] = useState(false);\n // Keep the latest tools without re-creating `run` on every render.\n const toolsRef = useRef(tools);\n toolsRef.current = tools;\n\n const run = useCallback(\n async (prompt: string): Promise<string> => {\n setIsRunning(true);\n setSteps([]);\n setAnswer(\"\");\n try {\n if (!engine.isReady) {\n await engine.load();\n }\n const { text } = await engine.generateWithTools(prompt, {\n tools: toolsRef.current,\n maxSteps,\n onStep: (step) => setSteps((prev) => [...prev, step]),\n });\n setAnswer(text);\n return text;\n } finally {\n setIsRunning(false);\n }\n },\n [engine, maxSteps],\n );\n\n const reset = useCallback(() => {\n setSteps([]);\n setAnswer(\"\");\n }, []);\n\n return {\n run,\n steps,\n answer,\n isRunning,\n isReady: engine.isReady,\n load: engine.load,\n reset,\n error: engine.error,\n };\n}\n","/**\n * React hook for debounced inline autocomplete (ghost text).\n *\n * Owns the debounce, in-flight, and stale-response guards so a component only has\n * to render the suggestion and handle accept/dismiss. Built on `useEngine`, so it\n * shares the same reference-counted engine as other hooks.\n *\n * @example\n * ```tsx\n * import { useAutocomplete } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { suggestion, onInput, accept, dismiss } = useAutocomplete({\n * model: \"mlx-community/Qwen3.5-0.8B-4bit\",\n * });\n * // <input onChange={(e) => onInput(e.target.value)} />\n * // render `suggestion` as ghost text; Tab → accept(), Esc → dismiss()\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useEngine } from \"./use-engine.js\";\n\nexport interface UseAutocompleteOptions {\n /** Model repo id to autocomplete with. */\n model: string;\n /** Debounce before fetching, in ms (default 550). */\n debounceMs?: number;\n /** Minimum trimmed input length before fetching (default 8). */\n minChars?: number;\n /** Max continuation tokens (default 16). */\n maxTokens?: number;\n /** Sampling temperature (default 0.3). */\n temperature?: number;\n /** Load the model on mount (default false — loads on first request). */\n autoLoad?: boolean;\n}\n\nexport interface UseAutocompleteReturn {\n /** Current ghost suggestion (continuation only), or \"\". */\n suggestion: string;\n /** True while a suggestion is being fetched. */\n isFetching: boolean;\n /** True once the model is loaded. */\n isReady: boolean;\n /** Load the model explicitly. */\n load: () => Promise<void>;\n /** Feed the latest input; schedules a debounced fetch and clears any stale suggestion. */\n onInput: (text: string) => void;\n /** Accept the current suggestion: returns it and clears state. */\n accept: () => string;\n /** Dismiss the current suggestion. */\n dismiss: () => void;\n /** Load/generation error, if any. */\n error: string | null;\n}\n\nexport function useAutocomplete(options: UseAutocompleteOptions): UseAutocompleteReturn {\n const {\n model,\n debounceMs = 550,\n minChars = 8,\n maxTokens = 16,\n temperature = 0.3,\n autoLoad = false,\n } = options;\n\n const engine = useEngine({ model, autoLoad });\n const [suggestion, setSuggestion] = useState(\"\");\n const [isFetching, setIsFetching] = useState(false);\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const inFlightRef = useRef(false);\n // Holds the value at request time so a stale response can't clobber newer input.\n const requestedForRef = useRef(\"\");\n\n const request = useCallback(\n async (text: string) => {\n if (inFlightRef.current) {\n return;\n }\n if (text.trim().length < minChars) {\n return;\n }\n inFlightRef.current = true;\n requestedForRef.current = text;\n setIsFetching(true);\n try {\n if (!engine.isReady) {\n await engine.load();\n }\n if (engine.error) {\n return;\n }\n const out = await engine.autocomplete(text, { maxTokens, temperature });\n // Discard if the user kept typing while we generated.\n if (requestedForRef.current !== text) {\n return;\n }\n if (out) {\n setSuggestion(out);\n }\n } finally {\n inFlightRef.current = false;\n setIsFetching(false);\n }\n },\n [engine, minChars, maxTokens, temperature],\n );\n\n const onInput = useCallback(\n (text: string) => {\n setSuggestion(\"\");\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n if (text.trim().length < minChars) {\n return;\n }\n debounceRef.current = setTimeout(() => void request(text), debounceMs);\n },\n [request, minChars, debounceMs],\n );\n\n const accept = useCallback(() => {\n const s = suggestion;\n setSuggestion(\"\");\n requestedForRef.current = \"\";\n return s;\n }, [suggestion]);\n\n const dismiss = useCallback(() => {\n setSuggestion(\"\");\n requestedForRef.current = \"\";\n }, []);\n\n useEffect(\n () => () => {\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n },\n [],\n );\n\n return {\n suggestion,\n isFetching,\n isReady: engine.isReady,\n load: engine.load,\n onInput,\n accept,\n dismiss,\n error: engine.error,\n };\n}\n","/**\n * React hook for on-device memory / RAG.\n *\n * Wraps the `@tryhamster/gerbil/memory` module with a native embedder (running\n * on the WebGPU engine) and a persistent IndexedDB store, so an agent can\n * remember things across turns AND across sessions — with zero server.\n *\n * ```tsx\n * import { useMemory } from \"@tryhamster/gerbil/hooks\";\n *\n * const memory = useMemory();\n * await memory.add(\"The user prefers TypeScript.\");\n * const { context } = await memory.recall(\"what does the user like?\", { tokenBudget: 256 });\n * ```\n *\n * The embedding model and the memory module are both imported lazily, so this\n * hook adds nothing to your bundle until it's used.\n */\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type {\n AddOptions,\n MemoryRecord,\n MemorySearchResult,\n RecallOptions,\n RecallResult,\n SearchOptions,\n} from \"../memory/types.js\";\nimport { useEngine } from \"./use-engine.js\";\n\n// Minimal structural type for the Memory facade we use (avoids importing the\n// memory barrel statically, which pulls a Node-only file store).\ninterface MemoryLike {\n add(text: string, options?: AddOptions): Promise<string[]>;\n recall(query: string, options?: RecallOptions): Promise<RecallResult>;\n search(query: string, options?: SearchOptions): Promise<MemorySearchResult[]>;\n get(id: string): Promise<MemoryRecord | undefined>;\n delete(id: string): Promise<boolean>;\n clear(): Promise<void>;\n size(): Promise<number>;\n}\n\nexport interface UseMemoryOptions {\n /** Embedding model repo (default: the built-in EmbeddingGemma). */\n model?: string;\n /** IndexedDB database name — use distinct names to isolate memories. */\n namespace?: string;\n}\n\nexport interface UseMemoryReturn {\n /** Store text (optionally chunked + tagged). Returns the new record id(s). */\n add: (text: string, options?: AddOptions) => Promise<string[]>;\n /** Retrieve a token-budgeted context block for a query. */\n recall: (query: string, options?: RecallOptions) => Promise<RecallResult>;\n /** Rank stored entries by similarity to a query. */\n search: (query: string, options?: SearchOptions) => Promise<MemorySearchResult[]>;\n /** Get a single record by id. */\n get: (id: string) => Promise<MemoryRecord | undefined>;\n /** Delete a record by id. */\n remove: (id: string) => Promise<boolean>;\n /** Clear all memories in this namespace. */\n clear: () => Promise<void>;\n /** Number of stored records. */\n size: () => Promise<number>;\n /** Whether the embedding model is downloading. */\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n /** Whether memory is ready (the embedding model has loaded). */\n isReady: boolean;\n error: string | null;\n}\n\nexport function useMemory(options: UseMemoryOptions = {}): UseMemoryReturn {\n const { model, namespace = \"gerbil-memory\" } = options;\n const embedder = useEngine({ model, embedding: true, autoLoad: false });\n const memRef = useRef<MemoryLike | null>(null);\n const initRef = useRef<Promise<MemoryLike> | null>(null);\n const [isReady, setIsReady] = useState(false);\n\n // Lazily build the Memory facade on first use: load the embedding model, then\n // wire it to an IndexedDB store. Imported from submodules (not the barrel) so\n // the Node-only file store is never pulled into the browser bundle.\n const ensure = useCallback((): Promise<MemoryLike> => {\n if (memRef.current) return Promise.resolve(memRef.current);\n if (initRef.current) return initRef.current;\n initRef.current = (async () => {\n if (!embedder.isReady) await embedder.load();\n const [{ createMemory }, { createIndexedDBStore }] = await Promise.all([\n import(\"../memory/memory.js\"),\n import(\"../memory/stores/indexeddb-store.js\"),\n ]);\n const mem = createMemory({\n embed: async (texts: string[]) => Promise.all(texts.map((t) => embedder.embed(t))),\n store: createIndexedDBStore({ dbName: namespace }),\n }) as unknown as MemoryLike;\n memRef.current = mem;\n setIsReady(true);\n return mem;\n })();\n return initRef.current;\n }, [embedder, namespace]);\n\n const add = useCallback(\n async (text: string, opts?: AddOptions) => (await ensure()).add(text, opts),\n [ensure],\n );\n const recall = useCallback(\n async (query: string, opts?: RecallOptions) => (await ensure()).recall(query, opts),\n [ensure],\n );\n const search = useCallback(\n async (query: string, opts?: SearchOptions) => (await ensure()).search(query, opts),\n [ensure],\n );\n const get = useCallback(async (id: string) => (await ensure()).get(id), [ensure]);\n const remove = useCallback(async (id: string) => (await ensure()).delete(id), [ensure]);\n const clear = useCallback(async () => (await ensure()).clear(), [ensure]);\n const size = useCallback(async () => (await ensure()).size(), [ensure]);\n\n return {\n add,\n recall,\n search,\n get,\n remove,\n clear,\n size,\n isLoading: embedder.isLoading,\n loadingProgress: embedder.loadingProgress,\n isReady,\n error: embedder.error,\n };\n}\n","/**\n * Per-modality convenience hooks built on {@link useEngine}.\n *\n * `useEngine` is the general/advanced hook (it can do text, vision, and\n * embeddings via options). These wrappers give each modality a focused,\n * self-documenting surface so app code reads cleanly:\n *\n * ```tsx\n * import { useText, useVision, useEmbedding } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { complete } = useText(); // text generation\n * const { describeImage } = useVision(); // image → text\n * const { embed, similarity } = useEmbedding(); // text → vector\n * ```\n *\n * They share the same engine registry as `useEngine`, so requesting the same\n * model from several places loads it once.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n type ChatMessage,\n type CompleteOptions,\n type EngineErrorKind,\n type GenerateObjectOptions,\n type UseEngineOptions,\n useEngine,\n} from \"./use-engine.js\";\n\n/** Options common to the modality hooks (the capability flag is set for you). */\nexport type ModalityOptions = Omit<UseEngineOptions, \"enableVision\" | \"embedding\">;\n\nexport interface UseTextReturn {\n complete: (prompt: string, options?: CompleteOptions) => Promise<string>;\n completion: string;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isGenerating: boolean;\n tps: number | null;\n error: string | null;\n errorKind: EngineErrorKind | null;\n isReady: boolean;\n load: () => Promise<void>;\n stop: () => void;\n dispose: () => void;\n}\n\n/** Text generation. */\nexport function useText(options: ModalityOptions = {}): UseTextReturn {\n const e = useEngine(options);\n return {\n complete: e.complete,\n completion: e.completion,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n isGenerating: e.isGenerating,\n tps: e.tps,\n error: e.error,\n errorKind: e.errorKind,\n isReady: e.isReady,\n load: e.load,\n stop: e.stop,\n dispose: e.dispose,\n };\n}\n\nexport interface UseObjectReturn<T = unknown> {\n /** The last successfully parsed + validated object (null until one is produced). */\n object: T | null;\n /**\n * Generate, parse JSON, validate against `schema`, and retry until valid.\n * Loads the model first if needed. Returns the parsed object.\n */\n generate: (prompt: string, options?: GenerateObjectOptions) => Promise<T>;\n /** Attempts the last `generate` call took (1 = first try). */\n attempts: number;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isGenerating: boolean;\n error: string | null;\n errorKind: EngineErrorKind | null;\n isReady: boolean;\n load: () => Promise<void>;\n stop: () => void;\n dispose: () => void;\n}\n\n/**\n * Structured-output generation — generate, parse JSON, validate, and RETRY\n * until valid. On-device tokens are free, so re-rolling malformed JSON is cheap.\n *\n * ```tsx\n * const { object, generate, isGenerating } = useObject<{ name: string; age: number }>();\n * await generate('Extract {name, age} from: \"I am Sarah, 28\"', {\n * schema: { required: [\"name\", \"age\"] },\n * });\n * // object === { name: \"Sarah\", age: 28 }\n * ```\n */\nexport function useObject<T = unknown>(options: ModalityOptions = {}): UseObjectReturn<T> {\n const e = useEngine(options);\n const [object, setObject] = useState<T | null>(null);\n\n const generate = useCallback(\n async (prompt: string, opts?: GenerateObjectOptions): Promise<T> => {\n if (!e.isReady) await e.load();\n const result = await e.generateObject<T>(prompt, opts);\n setObject(result.object);\n return result.object;\n },\n [e],\n );\n\n return {\n object,\n generate,\n attempts: e.attempts,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n isGenerating: e.isGenerating,\n error: e.error,\n errorKind: e.errorKind,\n isReady: e.isReady,\n load: e.load,\n stop: e.stop,\n dispose: e.dispose,\n };\n}\n\nexport interface UseVisionReturn {\n describeImage: UseEngineReturnLike[\"describeImage\"];\n completion: string;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isGenerating: boolean;\n tps: number | null;\n error: string | null;\n errorKind: EngineErrorKind | null;\n isReady: boolean;\n load: () => Promise<void>;\n stop: () => void;\n dispose: () => void;\n}\n\n// Local alias so the return types can reference describeImage/embed/similarity\n// signatures without re-declaring them.\ntype UseEngineReturnLike = ReturnType<typeof useEngine>;\n\n/** Image understanding (image in → text out). Builds the vision tower. */\nexport function useVision(options: ModalityOptions = {}): UseVisionReturn {\n const e = useEngine({ ...options, enableVision: true });\n return {\n describeImage: e.describeImage,\n completion: e.completion,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n isGenerating: e.isGenerating,\n tps: e.tps,\n error: e.error,\n errorKind: e.errorKind,\n isReady: e.isReady,\n load: e.load,\n stop: e.stop,\n dispose: e.dispose,\n };\n}\n\nexport interface UseEmbeddingReturn {\n embed: UseEngineReturnLike[\"embed\"];\n similarity: UseEngineReturnLike[\"similarity\"];\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n error: string | null;\n errorKind: EngineErrorKind | null;\n isReady: boolean;\n load: () => Promise<void>;\n dispose: () => void;\n}\n\n/** Text embeddings + similarity. */\nexport function useEmbedding(options: ModalityOptions = {}): UseEmbeddingReturn {\n const e = useEngine({ ...options, embedding: true });\n return {\n embed: e.embed,\n similarity: e.similarity,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n error: e.error,\n errorKind: e.errorKind,\n isReady: e.isReady,\n load: e.load,\n dispose: e.dispose,\n };\n}\n\nexport interface UseChatOptions extends ModalityOptions {\n /** System prompt prepended to every turn. */\n system?: string;\n}\n\n/** Chat lifecycle status (mirrors the Vercel AI SDK's `useChat` status). */\nexport type ChatStatus = \"ready\" | \"submitted\" | \"streaming\" | \"error\";\n\nexport interface UseChatReturn {\n /** The running conversation (user + assistant turns). */\n messages: ChatMessage[];\n /** Send a user message; the assistant reply streams in and is returned. */\n send: (text: string, options?: CompleteOptions) => Promise<string>;\n /** Alias of `send` (AI SDK-compatible name). */\n sendMessage: (text: string, options?: CompleteOptions) => Promise<string>;\n /** Re-run the last user turn (drops the previous assistant reply). */\n regenerate: (options?: CompleteOptions) => Promise<string>;\n /** Replace the conversation (value or updater). */\n setMessages: (next: ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[])) => void;\n /** Clear the conversation. */\n clear: () => void;\n /** Coarse lifecycle status: ready → submitted → streaming (→ error). */\n status: ChatStatus;\n isGenerating: boolean;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isReady: boolean;\n tps: number | null;\n error: string | null;\n errorKind: EngineErrorKind | null;\n stop: () => void;\n load: () => Promise<void>;\n}\n\n/**\n * Conversational chat hook — manages the message list and streams replies.\n * Multi-turn context is handled for you (the full history is sent each turn).\n *\n * ```tsx\n * const { messages, send, isGenerating } = useChat();\n * <button onClick={() => send(\"Hello!\")}>Send</button>\n * ```\n */\nexport function useChat(options: UseChatOptions = {}): UseChatReturn {\n const { system, ...engineOptions } = options;\n const e = useEngine(engineOptions);\n\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n // A ref mirror so send() always reads the latest history without a stale closure.\n const messagesRef = useRef<ChatMessage[]>([]);\n messagesRef.current = messages;\n\n // Stream the live completion into the trailing assistant turn as it generates.\n useEffect(() => {\n if (!e.isGenerating) return;\n setMessages((prev) => {\n if (prev.length === 0 || prev[prev.length - 1].role !== \"assistant\") return prev;\n const copy = prev.slice();\n copy[copy.length - 1] = { role: \"assistant\", content: e.completion };\n return copy;\n });\n }, [e.completion, e.isGenerating]);\n\n // Run a completion over the given history, streaming into the trailing\n // assistant turn, and return the final reply text.\n const run = useCallback(\n async (history: ChatMessage[], opts: CompleteOptions): Promise<string> => {\n setMessages([...history, { role: \"assistant\", content: \"\" }]);\n if (!e.isReady) await e.load();\n const turns: ChatMessage[] = system\n ? [{ role: \"system\", content: system }, ...history]\n : history;\n const full = await e.complete(turns, { ...opts, system: opts.system ?? system });\n setMessages((prev) => {\n if (prev.length === 0) return prev;\n const copy = prev.slice();\n copy[copy.length - 1] = { role: \"assistant\", content: full };\n return copy;\n });\n return full;\n },\n [e, system],\n );\n\n const send = useCallback(\n async (text: string, opts: CompleteOptions = {}): Promise<string> => {\n if (!text.trim() || e.isGenerating) return \"\";\n return run([...messagesRef.current, { role: \"user\", content: text }], opts);\n },\n [e.isGenerating, run],\n );\n\n const regenerate = useCallback(\n async (opts: CompleteOptions = {}): Promise<string> => {\n if (e.isGenerating) return \"\";\n // Drop trailing assistant turns; re-run from the last user message.\n const msgs = messagesRef.current.slice();\n while (msgs.length > 0 && msgs[msgs.length - 1].role === \"assistant\") msgs.pop();\n if (msgs.length === 0) return \"\";\n return run(msgs, opts);\n },\n [e.isGenerating, run],\n );\n\n const setMessagesPublic = useCallback(\n (next: ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[])) => setMessages(next),\n [],\n );\n const clear = useCallback(() => setMessages([]), []);\n\n const status: ChatStatus = e.error\n ? \"error\"\n : e.isGenerating\n ? e.completion.length === 0\n ? \"submitted\"\n : \"streaming\"\n : \"ready\";\n\n return {\n messages,\n send,\n sendMessage: send,\n regenerate,\n setMessages: setMessagesPublic,\n clear,\n status,\n isGenerating: e.isGenerating,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n isReady: e.isReady,\n tps: e.tps,\n error: e.error,\n errorKind: e.errorKind,\n stop: e.stop,\n load: e.load,\n };\n}\n\nexport interface UseCompletionReturn {\n /** The streamed completion text. */\n completion: string;\n /** Generate a completion for a prompt (loads the model if needed). */\n complete: (prompt: string, options?: CompleteOptions) => Promise<string>;\n /** Controlled input value (AI SDK-style). */\n input: string;\n setInput: (value: string) => void;\n handleInputChange: (e: { target: { value: string } }) => void;\n handleSubmit: (e?: { preventDefault?: () => void }) => void;\n isLoading: boolean;\n isReady: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n stop: () => void;\n error: string | null;\n load: () => Promise<void>;\n}\n\n/**\n * Single-prompt streaming completion with built-in input state — a near\n * drop-in for the Vercel AI SDK's `useCompletion`, running on-device.\n */\nexport function useCompletion(options: ModalityOptions = {}): UseCompletionReturn {\n const t = useText(options);\n const [input, setInput] = useState(\"\");\n\n const complete = useCallback(\n async (prompt: string, opts?: CompleteOptions): Promise<string> => {\n if (!t.isReady) await t.load();\n return t.complete(prompt, opts);\n },\n [t],\n );\n\n const handleInputChange = useCallback(\n (e: { target: { value: string } }) => setInput(e.target.value),\n [],\n );\n const handleSubmit = useCallback(\n (e?: { preventDefault?: () => void }) => {\n e?.preventDefault?.();\n const value = input;\n if (!value.trim()) return;\n setInput(\"\");\n void complete(value);\n },\n [input, complete],\n );\n\n return {\n completion: t.completion,\n complete,\n input,\n setInput,\n handleInputChange,\n handleSubmit,\n isLoading: t.isGenerating,\n isReady: t.isReady,\n loadingProgress: t.loadingProgress,\n stop: t.stop,\n error: t.error,\n load: t.load,\n };\n}\n","/**\n * React hook for native speech-to-text in the browser.\n *\n * Wraps `MoonshineSTT` — raw 16 kHz mono PCM in, transcript out (encoder-decoder\n * ASR, no streaming/partial API). This hook captures mic audio between\n * start/stop, resamples it to 16 kHz mono, and runs a single transcribe() on the\n * finalized utterance. The GPU engine is dynamically imported so it stays out of\n * the main bundle until STT is actually used.\n *\n * @example\n * ```tsx\n * import { useSTT } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { startRecording, stopRecording, transcript, isRecording } = useSTT();\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { DEFAULT_MODELS } from \"../gpu/defaults.js\";\n\ntype MoonshineSTTType = import(\"../gpu/index.js\").MoonshineSTT;\n\nconst MOONSHINE_SAMPLE_RATE = 16_000;\n\nexport interface UseSTTOptions {\n /** HF repo for the STT model (default: the built-in Moonshine). */\n repo?: string;\n /** Auto-load the model on mount (default: false — loads on first record). */\n autoLoad?: boolean;\n onReady?: () => void;\n onError?: (error: Error) => void;\n /** Called when a finalized utterance contains no speech (silence/noise). */\n onNoSpeech?: () => void;\n}\n\nexport interface UseSTTReturn {\n load: () => Promise<void>;\n startRecording: () => Promise<void>;\n stopRecording: () => Promise<void>;\n dispose: () => void;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isReady: boolean;\n isRecording: boolean;\n isTranscribing: boolean;\n transcript: string;\n audioSeconds: number | null;\n /** True when the last finalized utterance contained no speech. */\n noSpeech: boolean;\n error: string | null;\n}\n\n/** Downmix to mono and linearly resample a Float32 buffer to 16 kHz. */\nfunction toMono16k(channels: Float32Array[], inputRate: number): Float32Array {\n const inLen = channels[0]?.length ?? 0;\n const mono = new Float32Array(inLen);\n for (const ch of channels) {\n for (let i = 0; i < inLen; i++) mono[i] += ch[i] / channels.length;\n }\n if (inputRate === MOONSHINE_SAMPLE_RATE) return mono;\n const ratio = MOONSHINE_SAMPLE_RATE / inputRate;\n const outLen = Math.max(0, Math.floor(inLen * ratio));\n const out = new Float32Array(outLen);\n for (let i = 0; i < outLen; i++) {\n const srcPos = i / ratio;\n const i0 = Math.floor(srcPos);\n const i1 = Math.min(i0 + 1, inLen - 1);\n const frac = srcPos - i0;\n out[i] = mono[i0] * (1 - frac) + mono[i1] * frac;\n }\n return out;\n}\n\nexport function useSTT(options: UseSTTOptions = {}): UseSTTReturn {\n const { repo = DEFAULT_MODELS.stt, autoLoad = false, onReady, onError, onNoSpeech } = options;\n\n const sttRef = useRef<MoonshineSTTType | null>(null);\n const loadingRef = useRef(false);\n const mediaStreamRef = useRef<MediaStream | null>(null);\n const audioCtxRef = useRef<AudioContext | null>(null);\n const sourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const processorRef = useRef<ScriptProcessorNode | null>(null);\n const chunksRef = useRef<Float32Array[]>([]);\n const sampleRateRef = useRef<number>(MOONSHINE_SAMPLE_RATE);\n\n const [isLoading, setIsLoading] = useState(false);\n const [loadingProgress, setLoadingProgress] = useState<{\n status: string;\n progress?: number;\n } | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [isRecording, setIsRecording] = useState(false);\n const [isTranscribing, setIsTranscribing] = useState(false);\n const [transcript, setTranscript] = useState(\"\");\n const [audioSeconds, setAudioSeconds] = useState<number | null>(null);\n const [noSpeech, setNoSpeech] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n if (sttRef.current || loadingRef.current) return;\n loadingRef.current = true;\n\n if (typeof navigator === \"undefined\" || !(\"gpu\" in navigator)) {\n loadingRef.current = false;\n const err = new Error(\n \"WebGPU is not available in this browser. Native speech-to-text requires Chrome/Edge 113+, Firefox 141+, or Safari 26+.\",\n );\n setError(err.message);\n onError?.(err);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n setLoadingProgress({ status: \"Initializing speech-to-text...\" });\n\n try {\n const { MoonshineSTT } = await import(\"../gpu/index.js\");\n const stt = await MoonshineSTT.create({\n repo,\n onProgress: (loaded: number, total: number, message: string) => {\n setLoadingProgress({\n status: message,\n progress: total > 0 ? Math.round((loaded / total) * 100) : undefined,\n });\n },\n });\n sttRef.current = stt;\n setIsReady(true);\n setIsLoading(false);\n setLoadingProgress(null);\n onReady?.();\n } catch (e) {\n loadingRef.current = false;\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err.message);\n setIsLoading(false);\n setLoadingProgress(null);\n onError?.(err);\n }\n }, [repo, onReady, onError]);\n\n const teardownCapture = useCallback(() => {\n processorRef.current?.disconnect();\n sourceRef.current?.disconnect();\n if (audioCtxRef.current && audioCtxRef.current.state !== \"closed\") {\n void audioCtxRef.current.close();\n }\n for (const t of mediaStreamRef.current?.getTracks() ?? []) t.stop();\n processorRef.current = null;\n sourceRef.current = null;\n audioCtxRef.current = null;\n mediaStreamRef.current = null;\n }, []);\n\n const startRecording = useCallback(async () => {\n if (isRecording) return;\n if (!sttRef.current) await load();\n if (!sttRef.current) return; // load failed (error already set)\n\n setTranscript(\"\");\n setAudioSeconds(null);\n setNoSpeech(false);\n setError(null);\n chunksRef.current = [];\n\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n const name = (err as { name?: string }).name;\n if (name === \"NotAllowedError\" || name === \"SecurityError\") {\n setError(\"Microphone access denied. Allow mic access for this site and try again.\");\n } else if (name === \"NotFoundError\" || name === \"DevicesNotFoundError\") {\n setError(\"No microphone found. Connect a mic and try again.\");\n } else {\n setError(err.message);\n }\n onError?.(err);\n return;\n }\n mediaStreamRef.current = stream;\n\n const AudioCtx =\n (window as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext })\n .AudioContext ??\n (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioCtx) return;\n const ctx = new AudioCtx();\n audioCtxRef.current = ctx;\n sampleRateRef.current = ctx.sampleRate;\n\n const source = ctx.createMediaStreamSource(stream);\n sourceRef.current = source;\n\n // ScriptProcessor is deprecated but ubiquitous (incl. iOS Safari) and gives\n // raw Float32 PCM frames directly — exactly what Moonshine wants.\n const processor = ctx.createScriptProcessor(4096, 1, 1);\n processorRef.current = processor;\n processor.onaudioprocess = (ev) => {\n const input = ev.inputBuffer.getChannelData(0);\n chunksRef.current.push(new Float32Array(input));\n };\n source.connect(processor);\n processor.connect(ctx.destination);\n\n setIsRecording(true);\n }, [isRecording, load, onError]);\n\n const stopRecording = useCallback(async () => {\n if (!isRecording) return;\n setIsRecording(false);\n\n const inputRate = sampleRateRef.current;\n const captured = chunksRef.current;\n chunksRef.current = [];\n teardownCapture();\n\n const total = captured.reduce((n, c) => n + c.length, 0);\n const joined = new Float32Array(total);\n let off = 0;\n for (const c of captured) {\n joined.set(c, off);\n off += c.length;\n }\n const pcm = toMono16k([joined], inputRate);\n\n // Moonshine needs >= conv1 kernel size (127 samples).\n if (pcm.length < 127) {\n setError(\"Recording was too short. Hold the mic a moment longer.\");\n return;\n }\n\n setIsTranscribing(true);\n setError(null);\n try {\n const result = await (\n sttRef.current as MoonshineSTTType & {\n transcribe: (p: Float32Array) => Promise<{\n text: string;\n audioSeconds: number;\n noSpeech: boolean;\n speechRms: number;\n }>;\n }\n ).transcribe(pcm);\n setAudioSeconds(result.audioSeconds);\n setNoSpeech(result.noSpeech);\n setTranscript(result.text);\n if (result.noSpeech) {\n onNoSpeech?.();\n }\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err.message);\n onError?.(err);\n } finally {\n setIsTranscribing(false);\n }\n }, [isRecording, teardownCapture, onError, onNoSpeech]);\n\n const dispose = useCallback(() => {\n teardownCapture();\n if (sttRef.current) {\n (sttRef.current as MoonshineSTTType & { destroy?: () => void }).destroy?.();\n sttRef.current = null;\n loadingRef.current = false;\n setIsReady(false);\n }\n }, [teardownCapture]);\n\n useEffect(() => {\n if (autoLoad) void load();\n return () => {\n teardownCapture();\n if (sttRef.current) {\n (sttRef.current as MoonshineSTTType & { destroy?: () => void }).destroy?.();\n sttRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return {\n load,\n startRecording,\n stopRecording,\n dispose,\n isLoading,\n loadingProgress,\n isReady,\n isRecording,\n isTranscribing,\n transcript,\n audioSeconds,\n noSpeech,\n error,\n };\n}\n","/**\n * React hook for native text-to-speech in the browser.\n *\n * Wraps the engine's `speak()` (Kani-TTS-2) — the codec-LM backbone emits\n * NanoCodec audio tokens, the NanoCodec decoder turns them into 22.05 kHz mono\n * PCM, and this hook plays it through the Web Audio API (and keeps the clip for\n * instant replay). The GPU engine is dynamically imported so it stays out of the\n * main bundle until TTS is actually used.\n *\n * @example\n * ```tsx\n * import { useTTS } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { speak, isSynthesizing, isPlaying } = useTTS();\n * <button onClick={() => speak(\"Hello from on-device TTS.\")}>Speak</button>\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { DEFAULT_MODELS } from \"../gpu/defaults.js\";\n\ntype WebGPUEngineType = import(\"../gpu/index.js\").WebGPUEngine;\n\nconst KANI_SAMPLE_RATE = 22_050;\n\n/**\n * Built-in voices. Kani-TTS-2-en takes an `en_us`-style language tag prepended\n * to the text; the English checkpoint ships the US-English voice.\n */\nexport const KANI_VOICES = [{ value: \"en_us\", label: \"English (US)\" }] as const;\n\nexport type KaniVoice = (typeof KANI_VOICES)[number][\"value\"];\n\nexport interface SpeakOptions {\n /** Language/accent tag, e.g. \"en_us\". Prepended as \"{tag}: {text}\". */\n voice?: string;\n /** Sampling temperature (default 1.0). Higher = more expressive/varied. */\n temperature?: number;\n /** Top-p nucleus threshold (default 0.95). */\n topP?: number;\n /** Repetition penalty (default 1.1). */\n repetitionPenalty?: number;\n}\n\nexport interface UseTTSOptions {\n /** HF repo for the TTS model (default: the built-in Kani-TTS-2). */\n repo?: string;\n /** Auto-load the model on mount (default: false — loads on first speak). */\n autoLoad?: boolean;\n onReady?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface UseTTSReturn {\n load: () => Promise<void>;\n /** Synthesize + play `text`. Lazily loads the model on first call. */\n speak: (text: string, options?: SpeakOptions) => Promise<void>;\n /** Replay the most recently synthesized clip (no re-synthesis). */\n replay: () => Promise<void>;\n /** Stop any in-progress playback. */\n stop: () => void;\n dispose: () => void;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isReady: boolean;\n /** Synthesizing PCM (running the codec-LM + NanoCodec). */\n isSynthesizing: boolean;\n /** Audio is currently playing. */\n isPlaying: boolean;\n /** True once a clip has been synthesized and is available for replay. */\n hasAudio: boolean;\n /** Duration (seconds) of the last synthesized clip. */\n audioSeconds: number | null;\n /** Real-time factor of the last synthesis (audio-sec per wall-sec). */\n rtf: number | null;\n error: string | null;\n}\n\n/** Build an AudioBuffer from mono Float32 PCM at the given sample rate. */\nfunction pcmToAudioBuffer(ctx: AudioContext, pcm: Float32Array, sampleRate: number): AudioBuffer {\n const buffer = ctx.createBuffer(1, pcm.length, sampleRate);\n buffer.getChannelData(0).set(pcm);\n return buffer;\n}\n\nexport function useTTS(options: UseTTSOptions = {}): UseTTSReturn {\n const { repo = DEFAULT_MODELS.tts, autoLoad = false, onReady, onError } = options;\n\n const engineRef = useRef<WebGPUEngineType | null>(null);\n const loadingRef = useRef(false);\n const audioCtxRef = useRef<AudioContext | null>(null);\n const sourceRef = useRef<AudioBufferSourceNode | null>(null);\n const bufferRef = useRef<AudioBuffer | null>(null);\n\n const [isLoading, setIsLoading] = useState(false);\n const [loadingProgress, setLoadingProgress] = useState<{\n status: string;\n progress?: number;\n } | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [isSynthesizing, setIsSynthesizing] = useState(false);\n const [isPlaying, setIsPlaying] = useState(false);\n const [hasAudio, setHasAudio] = useState(false);\n const [audioSeconds, setAudioSeconds] = useState<number | null>(null);\n const [rtf, setRtf] = useState<number | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n if (engineRef.current || loadingRef.current) return;\n loadingRef.current = true;\n\n if (typeof navigator === \"undefined\" || !(\"gpu\" in navigator)) {\n loadingRef.current = false;\n const err = new Error(\n \"WebGPU is not available in this browser. Native text-to-speech requires Chrome/Edge 113+, Firefox 141+, or Safari 26+.\",\n );\n setError(err.message);\n onError?.(err);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n setLoadingProgress({ status: \"Initializing TTS...\" });\n\n try {\n const { WebGPUEngine } = await import(\"../gpu/index.js\");\n const engine = await WebGPUEngine.create({\n repo,\n onProgress: (loaded: number, total: number, message: string) => {\n setLoadingProgress({\n status: message,\n progress: total > 0 ? Math.round((loaded / total) * 100) : undefined,\n });\n },\n });\n engineRef.current = engine;\n setIsReady(true);\n setIsLoading(false);\n setLoadingProgress(null);\n onReady?.();\n } catch (e) {\n loadingRef.current = false;\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err.message);\n setIsLoading(false);\n setLoadingProgress(null);\n onError?.(err);\n }\n }, [repo, onReady, onError]);\n\n const stop = useCallback(() => {\n if (sourceRef.current) {\n try {\n sourceRef.current.onended = null;\n sourceRef.current.stop();\n } catch {\n // Already stopped.\n }\n sourceRef.current = null;\n }\n setIsPlaying(false);\n }, []);\n\n const playBuffer = useCallback(async () => {\n const buffer = bufferRef.current;\n if (!buffer) return;\n const AudioCtx =\n (window as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext })\n .AudioContext ??\n (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioCtx) return;\n if (!audioCtxRef.current || audioCtxRef.current.state === \"closed\") {\n audioCtxRef.current = new AudioCtx();\n }\n const ctx = audioCtxRef.current;\n if (!ctx) return;\n if (ctx.state === \"suspended\") await ctx.resume();\n stop();\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n source.connect(ctx.destination);\n source.onended = () => {\n setIsPlaying(false);\n sourceRef.current = null;\n };\n sourceRef.current = source;\n setIsPlaying(true);\n source.start();\n }, [stop]);\n\n const speak = useCallback(\n async (text: string, opts: SpeakOptions = {}): Promise<void> => {\n if (!text.trim()) return;\n // Unlock audio WITHIN the user gesture, BEFORE any await. speak() is called\n // straight from a click, but model load + synthesis take seconds; an\n // AudioContext first created/resumed after those awaits starts suspended on\n // iOS (and under Chrome's autoplay policy) and never makes sound. Create,\n // resume, and play a 1-sample silent buffer now, while the gesture is live.\n {\n const AudioCtx =\n (\n window as {\n AudioContext?: typeof AudioContext;\n webkitAudioContext?: typeof AudioContext;\n }\n ).AudioContext ??\n (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (AudioCtx) {\n if (!audioCtxRef.current || audioCtxRef.current.state === \"closed\") {\n audioCtxRef.current = new AudioCtx();\n }\n const ctx = audioCtxRef.current;\n if (ctx.state === \"suspended\") void ctx.resume();\n try {\n const warm = ctx.createBufferSource();\n warm.buffer = ctx.createBuffer(1, 1, ctx.sampleRate);\n warm.connect(ctx.destination);\n warm.start(0);\n } catch {\n /* warm-up best-effort */\n }\n }\n }\n if (!engineRef.current) await load();\n const engine = engineRef.current as\n | (WebGPUEngineType & {\n speak: (\n t: string,\n o: unknown,\n ) => Promise<{ pcm: Float32Array; sampleRate: number; audioSeconds: number }>;\n })\n | null;\n if (!engine) return; // load failed (error already set)\n\n setIsSynthesizing(true);\n setError(null);\n try {\n const t0 = performance.now();\n const {\n pcm,\n sampleRate,\n audioSeconds: secs,\n } = await engine.speak(text, {\n languageTag: opts.voice ?? \"en_us\",\n temperature: opts.temperature ?? 1.0,\n topP: opts.topP ?? 0.95,\n repetitionPenalty: opts.repetitionPenalty ?? 1.1,\n });\n const wall = (performance.now() - t0) / 1000;\n const AudioCtx =\n (\n window as {\n AudioContext?: typeof AudioContext;\n webkitAudioContext?: typeof AudioContext;\n }\n ).AudioContext ??\n (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (AudioCtx && (!audioCtxRef.current || audioCtxRef.current.state === \"closed\")) {\n audioCtxRef.current = new AudioCtx();\n }\n if (audioCtxRef.current) {\n bufferRef.current = pcmToAudioBuffer(\n audioCtxRef.current,\n pcm,\n sampleRate ?? KANI_SAMPLE_RATE,\n );\n }\n setHasAudio(true);\n setAudioSeconds(secs);\n setRtf(wall > 0 ? secs / wall : null);\n setIsSynthesizing(false);\n await playBuffer();\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err.message);\n setIsSynthesizing(false);\n onError?.(err);\n }\n },\n [load, playBuffer, onError],\n );\n\n const replay = useCallback(async () => {\n if (!bufferRef.current) return;\n await playBuffer();\n }, [playBuffer]);\n\n const dispose = useCallback(() => {\n stop();\n if (audioCtxRef.current && audioCtxRef.current.state !== \"closed\") {\n void audioCtxRef.current.close();\n }\n audioCtxRef.current = null;\n bufferRef.current = null;\n if (engineRef.current) {\n engineRef.current.destroy?.();\n engineRef.current = null;\n loadingRef.current = false;\n setIsReady(false);\n }\n }, [stop]);\n\n useEffect(() => {\n if (autoLoad) void load();\n return () => {\n if (sourceRef.current) {\n try {\n sourceRef.current.onended = null;\n sourceRef.current.stop();\n } catch {\n // Already stopped.\n }\n sourceRef.current = null;\n }\n if (audioCtxRef.current && audioCtxRef.current.state !== \"closed\") {\n void audioCtxRef.current.close();\n }\n if (engineRef.current) {\n engineRef.current.destroy?.();\n engineRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return {\n load,\n speak,\n replay,\n stop,\n dispose,\n isLoading,\n loadingProgress,\n isReady,\n isSynthesizing,\n isPlaying,\n hasAudio,\n audioSeconds,\n rtf,\n error,\n };\n}\n","/**\n * React hook for a fully on-device voice assistant: speak to it, it transcribes,\n * thinks, and speaks back — no cloud, no API keys. Composes {@link useSTT},\n * {@link useChat}, and {@link useTTS} into one flow.\n *\n * ```tsx\n * import { useVoiceChat } from \"@tryhamster/gerbil/hooks\";\n *\n * const vc = useVoiceChat();\n * <button onMouseDown={vc.start} onMouseUp={vc.stop}>\n * {vc.isListening ? \"Listening…\" : \"Hold to talk\"}\n * </button>\n * // vc.messages renders the conversation; replies are spoken automatically.\n * ```\n *\n * This is Gerbil-unique — a private, offline voice loop the cloud SDKs can't do.\n */\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport type { ChatMessage } from \"./use-engine.js\";\nimport { type UseChatOptions, useChat } from \"./use-modalities.js\";\nimport { useSTT } from \"./use-stt.js\";\nimport { useTTS } from \"./use-tts.js\";\n\nexport interface UseVoiceChatOptions extends UseChatOptions {\n /** Speech-to-text model repo (default: built-in Moonshine). */\n sttModel?: string;\n /** Text-to-speech model repo (default: built-in Kani-TTS-2). */\n ttsModel?: string;\n /** Voice for spoken replies (e.g. \"en_us\"). */\n voice?: string;\n /** Speak replies aloud (default: true). Set false for text-only. */\n speak?: boolean;\n}\n\nexport interface UseVoiceChatReturn {\n /** The running conversation. */\n messages: ChatMessage[];\n /** Start listening (opens the mic). */\n start: () => Promise<void>;\n /** Stop listening → transcribe → reply → speak. */\n stop: () => Promise<void>;\n /** Stop playback of the current spoken reply. */\n stopSpeaking: () => void;\n /** Clear the conversation. */\n clear: () => void;\n /** Mic is open and capturing. */\n isListening: boolean;\n /** Transcribing the captured audio. */\n isTranscribing: boolean;\n /** The model is generating a reply. */\n isThinking: boolean;\n /** A reply is being synthesized or played. */\n isSpeaking: boolean;\n /** The most recent transcribed user utterance. */\n transcript: string;\n /** Any model is still downloading. */\n isLoading: boolean;\n /** The chat model is ready. */\n isReady: boolean;\n error: string | null;\n}\n\nexport function useVoiceChat(options: UseVoiceChatOptions = {}): UseVoiceChatReturn {\n const { sttModel, ttsModel, voice, speak = true, ...chatOptions } = options;\n const stt = useSTT({ repo: sttModel });\n const chat = useChat(chatOptions);\n const tts = useTTS({ repo: ttsModel });\n\n // When a transcript lands (recording stopped, transcription done), feed it to\n // the model and speak the reply. A ref guards against re-processing the same\n // utterance on re-render.\n const processedRef = useRef(\"\");\n useEffect(() => {\n const text = stt.transcript.trim();\n if (!text || stt.isTranscribing || text === processedRef.current) return;\n processedRef.current = text;\n (async () => {\n const reply = await chat.send(text);\n if (speak && reply.trim()) await tts.speak(reply, voice ? { voice } : undefined);\n })();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [stt.transcript, stt.isTranscribing]);\n\n const start = useCallback(() => stt.startRecording(), [stt]);\n const stop = useCallback(() => stt.stopRecording(), [stt]);\n\n return {\n messages: chat.messages,\n start,\n stop,\n stopSpeaking: tts.stop,\n clear: chat.clear,\n isListening: stt.isRecording,\n isTranscribing: stt.isTranscribing,\n isThinking: chat.isGenerating,\n isSpeaking: tts.isSynthesizing || tts.isPlaying,\n transcript: stt.transcript,\n isLoading: stt.isLoading || chat.isLoading || tts.isLoading,\n isReady: chat.isReady,\n error: stt.error ?? chat.error ?? tts.error,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hooks.mjs","names":["created: SharedEngineEntry","state: GerbilGateState","turns: ChatMessage[]","stream: MediaStream"],"sources":["../../src/browser/use-engine.ts","../../src/browser/gerbil-gate.tsx","../../src/browser/use-agent.ts","../../src/browser/use-autocomplete.ts","../../src/browser/use-memory.ts","../../src/browser/use-modalities.ts","../../src/browser/use-stt.ts","../../src/browser/use-tts.ts","../../src/browser/use-voice-chat.ts"],"sourcesContent":["/**\n * React hook for native WebGPU inference in the browser.\n *\n * Uses gerbil's WebGPUEngine directly on the main thread — no web worker,\n * no ONNX Runtime, no transformers.js. Pure WGSL compute shaders.\n *\n * Handles the full engine lifecycle for you:\n * - loads the model (lazily or on mount),\n * - hot-swaps when you change `model`/`dtype`/`enableVision`/`embedding`,\n * - SHARES one engine across every component that asks for the same config\n * (reference-counted) so you never upload the same weights to the GPU twice,\n * - disposes when the last consumer unmounts.\n *\n * @example\n * ```tsx\n * import { useEngine } from \"@tryhamster/gerbil/browser\";\n *\n * function App() {\n * const { complete, completion, isLoading, isGenerating, tps } = useEngine({\n * model: \"mlx-community/Qwen3.5-0.8B-4bit\",\n * autoLoad: true,\n * });\n *\n * if (isLoading) return <div>Loading model...</div>;\n * return (\n * <div>\n * <button onClick={() => complete(\"What is 2+2?\")}>Generate</button>\n * <p>{completion}</p>\n * {isGenerating && <span>{tps?.toFixed(1)} tok/s</span>}\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { resolveDefaultRepo } from \"../gpu/defaults.js\";\n// STATIC import of the engine (NOT dynamic) so consumer bundlers can't re-chunk\n// it into an on-demand loader that hangs on iOS WebKit. See ./engine.ts.\nimport { WebGPUEngine } from \"./engine.js\";\n\ntype WebGPUEngineType = import(\"../gpu/index.js\").WebGPUEngine;\ntype AgentTool = import(\"../gpu/index.js\").AgentTool;\ntype AgentStep = import(\"../gpu/index.js\").AgentStep;\n\n/** A single conversation turn (matches the engine's chat message shape). */\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\nexport type EngineErrorKind =\n | \"no-webgpu\"\n | \"no-adapter\"\n | \"device-lost\"\n | \"oom\"\n | \"network\"\n | \"timeout\"\n | \"unknown\";\n\nexport interface UseEngineOptions {\n /**\n * HuggingFace repo ID (e.g., \"mlx-community/Qwen3.5-0.8B-4bit\"). Optional —\n * defaults to the built-in model for the requested capability (text, vision,\n * or embeddings).\n */\n model?: string;\n /** Max sequence length (default: auto — 2048 on mobile, 4096 on desktop). */\n maxSeqLen?: number;\n /**\n * Weight dtype. Default \"auto\": int4 on mobile (fits device memory), the\n * repo's native precision on desktop. Already-quantized repos stay q4.\n */\n dtype?: \"auto\" | \"f32\" | \"q4\";\n /** Auto-load the model on mount (default: false). */\n autoLoad?: boolean;\n /** Build the vision encoder so `describeImage()` works (vision checkpoints only). */\n enableVision?: boolean;\n /** Load as an embedding model so `embed()`/`similarity()` work. */\n embedding?: boolean;\n /** Loading timeout in ms (default: 300000 = 5 minutes). */\n loadingTimeout?: number;\n /** Called when the engine is ready. */\n onReady?: () => void;\n /** Called on error, with a coarse error kind for UI messaging. */\n onError?: (error: Error, kind: EngineErrorKind) => void;\n}\n\nexport interface CompleteOptions {\n /** Max tokens to generate (default: 256). */\n maxTokens?: number;\n /** Temperature (0 = greedy, default: 0.7). */\n temperature?: number;\n /** System prompt. */\n system?: string;\n /** Stop sequences. */\n stopSequences?: string[];\n}\n\nexport interface DescribeImageOptions extends CompleteOptions {\n /** Override the maximum tokens generated for the description. */\n maxTokens?: number;\n}\n\n/** Validator for {@link UseEngineReturn.generateObject}: a schema object or predicate. */\nexport type ObjectValidator<T = unknown> =\n | { required?: string[]; properties?: Record<string, unknown>; [key: string]: unknown }\n | ((o: T) => boolean);\n\nexport interface GenerateObjectOptions extends CompleteOptions {\n /**\n * Validation target. Either a predicate `(o) => boolean` or a minimal\n * JSON-schema-ish object with `required` keys. Omit to require valid JSON only.\n */\n schema?: ObjectValidator;\n /** Max retries after the first attempt (default: 4). */\n maxRetries?: number;\n}\n\nexport interface UseEngineReturn {\n /** Generate a completion for a prompt. Returns the full text. */\n complete: (prompt: string | ChatMessage[], options?: CompleteOptions) => Promise<string>;\n /** Inline autocomplete: continue `prefix` with a brief single-line completion. */\n autocomplete: (\n prefix: string,\n opts?: { maxTokens?: number; temperature?: number; stop?: string[]; singleLine?: boolean },\n ) => Promise<string>;\n /** Rewrite `text` in a target tone (or with free-form instructions). */\n rewrite: (\n text: string,\n opts?: { tone?: string; instructions?: string; maxTokens?: number; temperature?: number },\n ) => Promise<string>;\n /** Agentic tool-calling loop: generate → run tools → repeat until an answer. */\n generateWithTools: (\n prompt: string | ChatMessage[],\n opts: {\n tools: AgentTool[];\n maxSteps?: number;\n onStep?: (step: AgentStep) => void;\n maxTokens?: number;\n sampling?: { temperature?: number; topK?: number; topP?: number };\n },\n ) => Promise<{ text: string; steps: AgentStep[] }>;\n /**\n * Describe an image (image-in → text-out). Requires `enableVision: true`.\n * `image` may be a data/http(s) URL or pre-decoded RGB pixels.\n */\n describeImage: (\n image:\n | string\n | { pixels: Uint8ClampedArray | Uint8Array | Float32Array; width: number; height: number },\n prompt?: string,\n options?: DescribeImageOptions,\n ) => Promise<string>;\n /**\n * Generate a structured object: generate → parse JSON → validate → retry\n * until valid. Returns the parsed object plus the attempt count.\n */\n generateObject: <T = unknown>(\n prompt: string,\n options?: GenerateObjectOptions,\n ) => Promise<{ object: T; attempts: number }>;\n /** Embed text into an L2-normalized vector. Requires `embedding: true`. */\n embed: (text: string, options?: { taskType?: \"query\" | \"document\" }) => Promise<Float32Array>;\n /** Cosine similarity between two texts. Requires `embedding: true`. */\n similarity: (a: string, b: string) => Promise<number>;\n /** Current completion text (streams in token by token). */\n completion: string;\n /** Whether the model is loading. */\n isLoading: boolean;\n /** Loading progress info. */\n loadingProgress: { status: string; progress?: number } | null;\n /** Whether generation is in progress. */\n isGenerating: boolean;\n /** Current tokens per second. */\n tps: number | null;\n /** Attempts taken by the last `generateObject` call (0 until one runs). */\n attempts: number;\n /** Current error message. */\n error: string | null;\n /** Coarse error kind, for tailored UI messaging. */\n errorKind: EngineErrorKind | null;\n /** Whether the engine is ready. */\n isReady: boolean;\n /** Manually load the model. */\n load: () => Promise<void>;\n /** Stop current generation. */\n stop: () => void;\n /** Release this consumer's hold on the engine (disposes when no holders remain). */\n dispose: () => void;\n}\n\n// ── Error classification ────────────────────────────────────────────────\n\nfunction classifyError(err: Error): EngineErrorKind {\n const msg = err.message.toLowerCase();\n if (msg.includes(\"webgpu is not available\") || msg.includes(\"not supported\")) return \"no-webgpu\";\n if (msg.includes(\"no webgpu adapter\") || msg.includes(\"no gpu adapter\")) return \"no-adapter\";\n if (msg.includes(\"device lost\") || msg.includes(\"device was destroyed\")) return \"device-lost\";\n if (\n msg.includes(\"out of memory\") ||\n msg.includes(\"allocation failed\") ||\n msg.includes(\"buffer size\")\n )\n return \"oom\";\n if (msg.includes(\"fetch\") || msg.includes(\"network\") || msg.includes(\"cors\")) return \"network\";\n if (msg.includes(\"timeout\") || msg.includes(\"timed out\")) return \"timeout\";\n return \"unknown\";\n}\n\nfunction getErrorGuidance(kind: EngineErrorKind): string {\n switch (kind) {\n case \"no-webgpu\":\n return \"WebGPU requires Safari 26+ (iOS 26+), Chrome 113+, or Firefox 141+.\";\n case \"no-adapter\":\n return \"No GPU found. Try closing other browser tabs that might be using the GPU.\";\n case \"device-lost\":\n return \"GPU device was lost (tab may have been backgrounded). Please reload.\";\n case \"oom\":\n return \"Not enough GPU memory. Try a smaller model or close other tabs.\";\n case \"network\":\n return \"Failed to download model. Check your internet connection.\";\n case \"timeout\":\n return \"Model loading timed out. Check your connection or try a smaller model.\";\n default:\n return \"\";\n }\n}\n\nfunction getDefaultMaxSeqLen(): number {\n if (typeof navigator === \"undefined\") return 4096;\n const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);\n return isMobile ? 2048 : 4096;\n}\n\n// ── Module-level shared engine registry ─────────────────────────────────\n//\n// Components requesting the SAME (model|dtype|vision|embedding|maxSeqLen) share\n// ONE WebGPUEngine, reference-counted. This is what stops the page-narrator, the\n// highlight-to-explain feature, and the playground from each uploading the same\n// Qwen weights to the GPU. The engine is created once, awaited by all holders,\n// and destroyed when the last holder releases it.\n\ninterface SharedEngineEntry {\n promise: Promise<WebGPUEngineType>;\n engine: WebGPUEngineType | null;\n refs: number;\n /** Pending deferred-disposal timer (set when refs hit 0, cleared on re-acquire). */\n disposeTimer: ReturnType<typeof setTimeout> | null;\n}\n\nconst SHARED_ENGINES = new Map<string, SharedEngineEntry>();\n\n// Grace period before a released (refs===0) engine is actually destroyed. Lets a\n// transient unmount→remount — Next.js dynamic() + hydration, route changes, tab\n// switches — reuse the warm engine instead of destroying and re-creating it\n// (which uploads the weights to the GPU a second time). Re-acquiring within the\n// window cancels the disposal.\nconst ENGINE_DISPOSE_GRACE_MS = 30_000;\n\n// After a failed load, block an immediate re-load of the same key for this long.\n// Swallows the synchronous effect re-fire that would otherwise loop, while still\n// letting a user retry a few seconds later.\nconst RETRY_COOLDOWN_MS = 3000;\n\nfunction acquireSharedEngine(\n key: string,\n factory: () => Promise<WebGPUEngineType>,\n): Promise<WebGPUEngineType> {\n let entry = SHARED_ENGINES.get(key);\n if (!entry) {\n const created: SharedEngineEntry = {\n promise: factory(),\n engine: null,\n refs: 0,\n disposeTimer: null,\n };\n created.promise\n .then((eng) => {\n created.engine = eng;\n })\n .catch(() => {\n // Failed load: drop the entry so a later mount can retry from scratch.\n SHARED_ENGINES.delete(key);\n });\n SHARED_ENGINES.set(key, created);\n entry = created;\n }\n // Re-acquired within the grace window → cancel any pending disposal.\n if (entry.disposeTimer) {\n clearTimeout(entry.disposeTimer);\n entry.disposeTimer = null;\n }\n entry.refs += 1;\n return entry.promise;\n}\n\nfunction releaseSharedEngine(key: string): void {\n const entry = SHARED_ENGINES.get(key);\n if (!entry) return;\n entry.refs -= 1;\n if (entry.refs > 0 || entry.disposeTimer) return;\n // Defer destruction so a quick remount can reuse the warm engine. Only destroy\n // if still unreferenced when the timer fires.\n entry.disposeTimer = setTimeout(() => {\n entry.disposeTimer = null;\n if (entry.refs > 0) return;\n SHARED_ENGINES.delete(key);\n entry.promise\n .then((eng) => eng.destroy())\n .catch(() => {\n /* load already failed and was cleaned up */\n });\n }, ENGINE_DISPOSE_GRACE_MS);\n}\n\n/** Decode an image URL / data URL into RGB pixels via an offscreen canvas. */\nasync function decodeImage(\n src: string,\n): Promise<{ pixels: Uint8ClampedArray; width: number; height: number }> {\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject(new Error(\"Failed to load image.\"));\n img.src = src;\n });\n // Cap the longest side: ViT attention memory scales with patch-count², so a\n // full-resolution photo blows past the engine's maxVisionPatches. ≤448px keeps\n // it coherent at ~7× fewer patches.\n const MAX_DIM = 448;\n const scale = Math.min(1, MAX_DIM / Math.max(img.naturalWidth, img.naturalHeight));\n const canvas = document.createElement(\"canvas\");\n canvas.width = Math.max(1, Math.round(img.naturalWidth * scale));\n canvas.height = Math.max(1, Math.round(img.naturalHeight * scale));\n const cctx = canvas.getContext(\"2d\");\n if (!cctx) throw new Error(\"Could not get 2D canvas context for image decode.\");\n cctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n const data = cctx.getImageData(0, 0, canvas.width, canvas.height);\n const rgba = data.data;\n const rgb = new Uint8ClampedArray(canvas.width * canvas.height * 3);\n for (let i = 0, j = 0; i < rgba.length; i += 4, j += 3) {\n rgb[j] = rgba[i];\n rgb[j + 1] = rgba[i + 1];\n rgb[j + 2] = rgba[i + 2];\n }\n return { pixels: rgb, width: canvas.width, height: canvas.height };\n}\n\nexport function useEngine(options: UseEngineOptions = {}): UseEngineReturn {\n const {\n model: modelOption,\n maxSeqLen,\n dtype = \"auto\",\n autoLoad = false,\n enableVision = false,\n embedding = false,\n loadingTimeout = 300_000,\n onReady,\n onError,\n } = options;\n // Resolve the default model for the requested capability when none is given.\n const model = resolveDefaultRepo({ repo: modelOption, embedding, enableVision });\n\n const engineRef = useRef<WebGPUEngineType | null>(null);\n const stoppedRef = useRef(false);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n // The shared-registry key this hook instance currently holds a ref on.\n const heldKeyRef = useRef<string | null>(null);\n // A key whose load failed, with the timestamp of the failure. Consumers call\n // load() from an effect gated on `!isReady && !isLoading`, so a single failed\n // load would otherwise re-fire instantly forever (create → fail → create → …).\n // We block re-loading the SAME key only within a short cooldown — long enough\n // to swallow that instant effect re-fire, short enough that a user retrying\n // (selecting text again, clicking generate) a moment later actually retries.\n const failedKeyRef = useRef<{ key: string; at: number } | null>(null);\n\n const [isLoading, setIsLoading] = useState(false);\n const [loadingProgress, setLoadingProgress] = useState<{\n status: string;\n progress?: number;\n } | null>(null);\n const [isGenerating, setIsGenerating] = useState(false);\n const [isReady, setIsReady] = useState(false);\n const [completion, setCompletion] = useState(\"\");\n const [tps, setTps] = useState<number | null>(null);\n const [attempts, setAttempts] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [errorKind, setErrorKind] = useState<EngineErrorKind | null>(null);\n\n const modelKey = `${model}|${dtype}|${enableVision}|${embedding}|${maxSeqLen ?? \"auto\"}`;\n\n const fail = useCallback(\n (e: unknown) => {\n const err = e instanceof Error ? e : new Error(String(e));\n const kind = classifyError(err);\n const guidance = getErrorGuidance(kind);\n setError(guidance ? `${err.message} ${guidance}` : err.message);\n setErrorKind(kind);\n setIsLoading(false);\n setLoadingProgress(null);\n onError?.(err, kind);\n },\n [onError],\n );\n\n const load = useCallback(async () => {\n const failed = failedKeyRef.current;\n const inCooldown = failed?.key === modelKey && Date.now() - failed.at < RETRY_COOLDOWN_MS;\n if (engineRef.current || heldKeyRef.current === modelKey || inCooldown) return;\n\n // Pre-check WebGPU before the expensive dynamic import.\n if (typeof navigator === \"undefined\" || !(\"gpu\" in navigator)) {\n failedKeyRef.current = { key: modelKey, at: Date.now() };\n fail(new Error(\"WebGPU is not available in this browser.\"));\n return;\n }\n\n setIsLoading(true);\n setError(null);\n setErrorKind(null);\n setLoadingProgress({ status: \"Initializing WebGPU engine...\" });\n\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutRef.current = setTimeout(\n () => reject(new Error(\"Model loading timed out. The download may be too slow.\")),\n loadingTimeout,\n );\n });\n\n const key = modelKey;\n heldKeyRef.current = key;\n\n const factory = async () => {\n return WebGPUEngine.create({\n repo: model,\n maxSeqLen: maxSeqLen ?? getDefaultMaxSeqLen(),\n dtype,\n enableVision,\n embedding,\n onProgress: (loaded: number, total: number, message: string) => {\n setLoadingProgress({\n status: message,\n progress: total > 0 ? Math.round((loaded / total) * 100) : undefined,\n });\n },\n });\n };\n\n try {\n const engine = await Promise.race([acquireSharedEngine(key, factory), timeoutPromise]);\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n // A swap may have superseded this load while it was in flight.\n if (heldKeyRef.current !== key) {\n releaseSharedEngine(key);\n return;\n }\n engineRef.current = engine;\n failedKeyRef.current = null;\n if (typeof window !== \"undefined\")\n (window as { __gerbilEngine?: unknown }).__gerbilEngine = engine;\n setIsReady(true);\n setIsLoading(false);\n setLoadingProgress(null);\n onReady?.();\n } catch (e) {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n // Record the failure (with timestamp) so the consumer's load effect can't\n // instantly re-fire into a tight loop, but a user retry after the cooldown\n // still works. Release the hold so a DIFFERENT key (config change) can load.\n failedKeyRef.current = { key, at: Date.now() };\n if (heldKeyRef.current === key) heldKeyRef.current = null;\n releaseSharedEngine(key);\n fail(e);\n }\n }, [modelKey, model, maxSeqLen, dtype, enableVision, embedding, loadingTimeout, onReady, fail]);\n\n const stop = useCallback(() => {\n stoppedRef.current = true;\n }, []);\n\n const dispose = useCallback(() => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n engineRef.current = null;\n failedKeyRef.current = null;\n setIsReady(false);\n if (heldKeyRef.current) {\n releaseSharedEngine(heldKeyRef.current);\n heldKeyRef.current = null;\n }\n }, []);\n\n const complete = useCallback(\n async (prompt: string | ChatMessage[], opts: CompleteOptions = {}): Promise<string> => {\n const engine = engineRef.current;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n setIsGenerating(true);\n setCompletion(\"\");\n setTps(null);\n stoppedRef.current = false;\n let fullText = \"\";\n try {\n const result = await engine.generate(prompt, {\n maxTokens: opts.maxTokens ?? 256,\n sampling: { temperature: opts.temperature ?? 0.7 },\n systemPrompt: opts.system,\n stopSequences: opts.stopSequences,\n onToken: (token, meta) => {\n if (stoppedRef.current) return;\n fullText += token;\n setCompletion(fullText);\n // Live decode-only tok/s, updated each token during generation.\n if (meta) setTps(meta.tps);\n },\n });\n // Final accurate value (decode-only) once generation completes.\n setTps(result.tokensPerSecond);\n setIsGenerating(false);\n return result.text;\n } catch (e) {\n setIsGenerating(false);\n fail(e);\n return fullText;\n }\n },\n [fail],\n );\n\n const autocomplete = useCallback(\n async (\n prefix: string,\n opts?: { maxTokens?: number; temperature?: number; stop?: string[]; singleLine?: boolean },\n ): Promise<string> => {\n const engine = engineRef.current;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n return engine.autocomplete(prefix, opts);\n },\n [],\n );\n\n const rewrite = useCallback(\n async (\n text: string,\n opts?: { tone?: string; instructions?: string; maxTokens?: number; temperature?: number },\n ): Promise<string> => {\n const engine = engineRef.current;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n return engine.rewrite(text, opts);\n },\n [],\n );\n\n const generateWithTools = useCallback(\n (\n prompt: string | ChatMessage[],\n opts: {\n tools: AgentTool[];\n maxSteps?: number;\n onStep?: (step: AgentStep) => void;\n maxTokens?: number;\n sampling?: { temperature?: number; topK?: number; topP?: number };\n },\n ) => {\n const engine = engineRef.current;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n return engine.generateWithTools(prompt, opts);\n },\n [],\n );\n\n const generateObject = useCallback(\n async <T = unknown>(\n prompt: string,\n opts: GenerateObjectOptions = {},\n ): Promise<{ object: T; attempts: number }> => {\n const engine = engineRef.current as\n | (WebGPUEngineType & {\n generateObject: (\n p: string,\n o: unknown,\n ) => Promise<{ object: unknown; text: string; attempts: number }>;\n })\n | null;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n setIsGenerating(true);\n setCompletion(\"\");\n setTps(null);\n setAttempts(0);\n stoppedRef.current = false;\n try {\n const result = await engine.generateObject(prompt, {\n schema: opts.schema,\n maxRetries: opts.maxRetries,\n maxTokens: opts.maxTokens ?? 256,\n sampling: { temperature: opts.temperature ?? 0.7 },\n systemPrompt: opts.system,\n stopSequences: opts.stopSequences,\n });\n setCompletion(result.text);\n setAttempts(result.attempts);\n setIsGenerating(false);\n return { object: result.object as T, attempts: result.attempts };\n } catch (e) {\n setIsGenerating(false);\n fail(e);\n throw e instanceof Error ? e : new Error(String(e));\n }\n },\n [fail],\n );\n\n const describeImage = useCallback(\n async (\n image:\n | string\n | { pixels: Uint8ClampedArray | Uint8Array | Float32Array; width: number; height: number },\n prompt = \"Describe this image.\",\n opts: DescribeImageOptions = {},\n ): Promise<string> => {\n const engine = engineRef.current as\n | (WebGPUEngineType & {\n hasVision: boolean;\n describeImage: (\n img: unknown,\n p: string,\n o: unknown,\n ) => Promise<{ text: string; tokensPerSecond: number }>;\n })\n | null;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n if (!engine.hasVision) throw new Error(\"Engine was not created with enableVision: true.\");\n setIsGenerating(true);\n setCompletion(\"\");\n setTps(null);\n stoppedRef.current = false;\n const decoded = typeof image === \"string\" ? await decodeImage(image) : image;\n let fullText = \"\";\n try {\n const result = await engine.describeImage(decoded, prompt, {\n maxTokens: opts.maxTokens ?? 150,\n sampling: { temperature: opts.temperature ?? 0.7 },\n systemPrompt: opts.system,\n stopSequences: opts.stopSequences,\n onToken: (token, meta) => {\n if (stoppedRef.current) return;\n fullText += token;\n setCompletion(fullText);\n // Live decode-only tok/s, updated each token during generation.\n if (meta) setTps(meta.tps);\n },\n });\n // Final accurate value (decode-only) once generation completes.\n setTps(result.tokensPerSecond);\n setIsGenerating(false);\n return result.text;\n } catch (e) {\n setIsGenerating(false);\n fail(e);\n return fullText;\n }\n },\n [fail],\n );\n\n const embed = useCallback(\n async (text: string, opts: { taskType?: \"query\" | \"document\" } = {}): Promise<Float32Array> => {\n const engine = engineRef.current as\n | (WebGPUEngineType & {\n isEmbedding: boolean;\n embed: (t: string, o: unknown) => Promise<Float32Array>;\n })\n | null;\n if (!engine) throw new Error(\"Engine not loaded. Call load() first.\");\n if (!engine.isEmbedding) throw new Error(\"Engine was not created with embedding: true.\");\n return engine.embed(text, { taskType: opts.taskType ?? \"query\" });\n },\n [],\n );\n\n const similarity = useCallback(\n async (a: string, b: string): Promise<number> => {\n const [va, vb] = await Promise.all([\n embed(a, { taskType: \"query\" }),\n embed(b, { taskType: \"document\" }),\n ]);\n let dot = 0;\n const n = Math.min(va.length, vb.length);\n for (let i = 0; i < n; i++) dot += va[i] * vb[i];\n return dot;\n },\n [embed],\n );\n\n // Initial autoLoad + teardown on unmount.\n useEffect(() => {\n if (autoLoad) load();\n return () => {\n if (timeoutRef.current) clearTimeout(timeoutRef.current);\n engineRef.current = null;\n if (heldKeyRef.current) {\n releaseSharedEngine(heldKeyRef.current);\n heldKeyRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Hot-swap when the config changes. Fires whenever an engine is already held\n // (whether loaded eagerly via autoLoad or lazily via a manual load()) and the\n // config differs — release the old engine and load the new one. If nothing is\n // loaded yet (heldKeyRef === null), do nothing, so lazy consumers stay lazy.\n useEffect(() => {\n if (heldKeyRef.current === modelKey) return; // current engine matches\n // This effect runs ONLY when modelKey changes, so reaching here means the\n // config genuinely changed — a previously-failed key no longer applies.\n failedKeyRef.current = null;\n if (heldKeyRef.current === null) return; // nothing loaded; consumer loads if it wants\n engineRef.current = null;\n releaseSharedEngine(heldKeyRef.current);\n heldKeyRef.current = null;\n setIsReady(false);\n setCompletion(\"\");\n setTps(null);\n setError(null);\n setErrorKind(null);\n load();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [modelKey]);\n\n return {\n complete,\n autocomplete,\n rewrite,\n generateWithTools,\n generateObject,\n describeImage,\n embed,\n similarity,\n completion,\n isLoading,\n loadingProgress,\n isGenerating,\n tps,\n attempts,\n error,\n errorKind,\n isReady,\n load,\n stop,\n dispose,\n };\n}\n","/**\n * Gate your app behind the model download.\n *\n * Wrap your app (or a section) in `<GerbilGate>` to show a splash / loading\n * screen while the engine downloads + initializes, then render children once it\n * is ready — so by the time your app renders, you KNOW the engine is live.\n *\n * Because the gate stays mounted (typically at the root), it also keeps the\n * shared engine warm for the lifetime of the app: the reference never drops to\n * zero, so client-side navigation never re-uploads the weights to the GPU or\n * hits the 30s teardown window. Fully optional — the inline `await load()` /\n * `autoLoad` pattern still works.\n *\n * @example\n * ```tsx\n * import { GerbilGate } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * <GerbilGate\n * model=\"mlx-community/Qwen3.5-0.8B-4bit\"\n * fallback={({ progress, status }) => (\n * <Splash>{status} {progress != null && `${progress}%`}</Splash>\n * )}\n * errorFallback={({ error }) => <ErrorScreen message={error} />}\n * >\n * <App />\n * </GerbilGate>\n * ```\n */\n\nimport type { ReactNode } from \"react\";\nimport type { EngineErrorKind } from \"./use-engine.js\";\nimport { useEngine } from \"./use-engine.js\";\n\n/** Load state passed to the gate's `fallback` / `errorFallback` render functions. */\nexport interface GerbilGateState {\n isLoading: boolean;\n /** 0–100 download progress, or `null` if not yet known. */\n progress: number | null;\n /** Human-readable status (e.g. \"Downloading model…\"), or `null`. */\n status: string | null;\n /** Error message if the engine failed to load, or `null`. */\n error: string | null;\n /** Coarse error kind for UI branching (e.g. \"no-webgpu\"), or `null`. */\n errorKind: EngineErrorKind | null;\n}\n\ntype GateSlot = ReactNode | ((state: GerbilGateState) => ReactNode);\n\nexport interface GerbilGateProps {\n /** HuggingFace repo id to preload (defaults to the built-in text model). */\n model?: string;\n /** Rendered once the engine is ready. */\n children: ReactNode;\n /** Splash / loading UI shown until ready. A node, or a render fn given load state. */\n fallback?: GateSlot;\n /** Rendered if the engine fails to load. Falls back to `fallback` when omitted. */\n errorFallback?: GateSlot;\n /** Load as an embedding model (`embed()` / `similarity()`). */\n embedding?: boolean;\n /** Build the vision encoder so `describeImage()` works (vision checkpoints only). */\n enableVision?: boolean;\n /** Weight dtype (default \"auto\": int4 on mobile, native on desktop). */\n dtype?: \"auto\" | \"f32\" | \"q4\";\n /** Max sequence length (default: auto). */\n maxSeqLen?: number;\n}\n\nfunction renderSlot(slot: GateSlot, state: GerbilGateState): ReactNode {\n return typeof slot === \"function\" ? slot(state) : slot;\n}\n\nexport function GerbilGate({\n model,\n children,\n fallback = null,\n errorFallback,\n embedding,\n enableVision,\n dtype,\n maxSeqLen,\n}: GerbilGateProps) {\n const engine = useEngine({\n model,\n embedding,\n enableVision,\n dtype,\n maxSeqLen,\n autoLoad: true,\n });\n\n const state: GerbilGateState = {\n isLoading: engine.isLoading,\n progress: engine.loadingProgress?.progress ?? null,\n status: engine.loadingProgress?.status ?? null,\n error: engine.error,\n errorKind: engine.errorKind,\n };\n\n if (engine.error) {\n return <>{renderSlot(errorFallback ?? fallback, state)}</>;\n }\n if (!engine.isReady) {\n return <>{renderSlot(fallback, state)}</>;\n }\n return <>{children}</>;\n}\n","/**\n * React hook for agentic tool-calling in the browser.\n *\n * Owns the agent loop state (running flag, step trace, final answer) on top of\n * `useEngine().generateWithTools`. You supply the model + tools; the hook runs the\n * generate → call-tool → feed-result loop and exposes the steps for trace UIs.\n *\n * @example\n * ```tsx\n * import { useAgent } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { run, steps, answer, isRunning } = useAgent({\n * model: \"mlx-community/Qwen3.5-0.8B-4bit\",\n * tools: [weatherTool],\n * });\n * await run(\"What's the weather in Paris?\");\n * ```\n */\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type { AgentStep, AgentTool } from \"../gpu/index.js\";\nimport { useEngine } from \"./use-engine.js\";\n\nexport interface UseAgentOptions {\n /** Model repo id to run the agent with. */\n model: string;\n /** Tools the agent may call. */\n tools: AgentTool[];\n /** Max generate → tool → result rounds before giving up (default 5). */\n maxSteps?: number;\n /** Load the model on mount (default false — loads on first run). */\n autoLoad?: boolean;\n}\n\nexport interface UseAgentReturn {\n /** Run the agent on a prompt; resolves to the final answer text. */\n run: (prompt: string) => Promise<string>;\n /** The step trace of the most recent run (tool calls, results, final answer). */\n steps: AgentStep[];\n /** Final answer text of the most recent run. */\n answer: string;\n /** True while the agent loop is running. */\n isRunning: boolean;\n /** True once the model is loaded. */\n isReady: boolean;\n /** Load the model explicitly. */\n load: () => Promise<void>;\n /** Clear the steps + answer. */\n reset: () => void;\n /** Load/run error, if any. */\n error: string | null;\n}\n\nexport function useAgent(options: UseAgentOptions): UseAgentReturn {\n const { model, tools, maxSteps = 5, autoLoad = false } = options;\n const engine = useEngine({ model, autoLoad });\n const [steps, setSteps] = useState<AgentStep[]>([]);\n const [answer, setAnswer] = useState(\"\");\n const [isRunning, setIsRunning] = useState(false);\n // Keep the latest tools without re-creating `run` on every render.\n const toolsRef = useRef(tools);\n toolsRef.current = tools;\n\n const run = useCallback(\n async (prompt: string): Promise<string> => {\n setIsRunning(true);\n setSteps([]);\n setAnswer(\"\");\n try {\n if (!engine.isReady) {\n await engine.load();\n }\n const { text } = await engine.generateWithTools(prompt, {\n tools: toolsRef.current,\n maxSteps,\n onStep: (step) => setSteps((prev) => [...prev, step]),\n });\n setAnswer(text);\n return text;\n } finally {\n setIsRunning(false);\n }\n },\n [engine, maxSteps],\n );\n\n const reset = useCallback(() => {\n setSteps([]);\n setAnswer(\"\");\n }, []);\n\n return {\n run,\n steps,\n answer,\n isRunning,\n isReady: engine.isReady,\n load: engine.load,\n reset,\n error: engine.error,\n };\n}\n","/**\n * React hook for debounced inline autocomplete (ghost text).\n *\n * Owns the debounce, in-flight, and stale-response guards so a component only has\n * to render the suggestion and handle accept/dismiss. Built on `useEngine`, so it\n * shares the same reference-counted engine as other hooks.\n *\n * @example\n * ```tsx\n * import { useAutocomplete } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { suggestion, onInput, accept, dismiss } = useAutocomplete({\n * model: \"mlx-community/Qwen3.5-0.8B-4bit\",\n * });\n * // <input onChange={(e) => onInput(e.target.value)} />\n * // render `suggestion` as ghost text; Tab → accept(), Esc → dismiss()\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useEngine } from \"./use-engine.js\";\n\nexport interface UseAutocompleteOptions {\n /** Model repo id to autocomplete with. */\n model: string;\n /** Debounce before fetching, in ms (default 550). */\n debounceMs?: number;\n /** Minimum trimmed input length before fetching (default 8). */\n minChars?: number;\n /** Max continuation tokens (default 16). */\n maxTokens?: number;\n /** Sampling temperature (default 0.3). */\n temperature?: number;\n /** Load the model on mount (default false — loads on first request). */\n autoLoad?: boolean;\n}\n\nexport interface UseAutocompleteReturn {\n /** Current ghost suggestion (continuation only), or \"\". */\n suggestion: string;\n /** True while a suggestion is being fetched. */\n isFetching: boolean;\n /** True once the model is loaded. */\n isReady: boolean;\n /** Load the model explicitly. */\n load: () => Promise<void>;\n /** Feed the latest input; schedules a debounced fetch and clears any stale suggestion. */\n onInput: (text: string) => void;\n /** Accept the current suggestion: returns it and clears state. */\n accept: () => string;\n /** Dismiss the current suggestion. */\n dismiss: () => void;\n /** Load/generation error, if any. */\n error: string | null;\n}\n\nexport function useAutocomplete(options: UseAutocompleteOptions): UseAutocompleteReturn {\n const {\n model,\n debounceMs = 550,\n minChars = 8,\n maxTokens = 16,\n temperature = 0.3,\n autoLoad = false,\n } = options;\n\n const engine = useEngine({ model, autoLoad });\n const [suggestion, setSuggestion] = useState(\"\");\n const [isFetching, setIsFetching] = useState(false);\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const inFlightRef = useRef(false);\n // Holds the value at request time so a stale response can't clobber newer input.\n const requestedForRef = useRef(\"\");\n\n const request = useCallback(\n async (text: string) => {\n if (inFlightRef.current) {\n return;\n }\n if (text.trim().length < minChars) {\n return;\n }\n inFlightRef.current = true;\n requestedForRef.current = text;\n setIsFetching(true);\n try {\n if (!engine.isReady) {\n await engine.load();\n }\n if (engine.error) {\n return;\n }\n const out = await engine.autocomplete(text, { maxTokens, temperature });\n // Discard if the user kept typing while we generated.\n if (requestedForRef.current !== text) {\n return;\n }\n if (out) {\n setSuggestion(out);\n }\n } finally {\n inFlightRef.current = false;\n setIsFetching(false);\n }\n },\n [engine, minChars, maxTokens, temperature],\n );\n\n const onInput = useCallback(\n (text: string) => {\n setSuggestion(\"\");\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n if (text.trim().length < minChars) {\n return;\n }\n debounceRef.current = setTimeout(() => void request(text), debounceMs);\n },\n [request, minChars, debounceMs],\n );\n\n const accept = useCallback(() => {\n const s = suggestion;\n setSuggestion(\"\");\n requestedForRef.current = \"\";\n return s;\n }, [suggestion]);\n\n const dismiss = useCallback(() => {\n setSuggestion(\"\");\n requestedForRef.current = \"\";\n }, []);\n\n useEffect(\n () => () => {\n if (debounceRef.current) {\n clearTimeout(debounceRef.current);\n }\n },\n [],\n );\n\n return {\n suggestion,\n isFetching,\n isReady: engine.isReady,\n load: engine.load,\n onInput,\n accept,\n dismiss,\n error: engine.error,\n };\n}\n","/**\n * React hook for on-device memory / RAG.\n *\n * Wraps the `@tryhamster/gerbil/memory` module with a native embedder (running\n * on the WebGPU engine) and a persistent IndexedDB store, so an agent can\n * remember things across turns AND across sessions — with zero server.\n *\n * ```tsx\n * import { useMemory } from \"@tryhamster/gerbil/hooks\";\n *\n * const memory = useMemory();\n * await memory.add(\"The user prefers TypeScript.\");\n * const { context } = await memory.recall(\"what does the user like?\", { tokenBudget: 256 });\n * ```\n *\n * The embedding model and the memory module are both imported lazily, so this\n * hook adds nothing to your bundle until it's used.\n */\n\nimport { useCallback, useRef, useState } from \"react\";\nimport type {\n AddOptions,\n MemoryRecord,\n MemorySearchResult,\n RecallOptions,\n RecallResult,\n SearchOptions,\n} from \"../memory/types.js\";\nimport { useEngine } from \"./use-engine.js\";\n\n// Minimal structural type for the Memory facade we use (avoids importing the\n// memory barrel statically, which pulls a Node-only file store).\ninterface MemoryLike {\n add(text: string, options?: AddOptions): Promise<string[]>;\n recall(query: string, options?: RecallOptions): Promise<RecallResult>;\n search(query: string, options?: SearchOptions): Promise<MemorySearchResult[]>;\n get(id: string): Promise<MemoryRecord | undefined>;\n delete(id: string): Promise<boolean>;\n clear(): Promise<void>;\n size(): Promise<number>;\n}\n\nexport interface UseMemoryOptions {\n /** Embedding model repo (default: the built-in EmbeddingGemma). */\n model?: string;\n /** IndexedDB database name — use distinct names to isolate memories. */\n namespace?: string;\n}\n\nexport interface UseMemoryReturn {\n /** Store text (optionally chunked + tagged). Returns the new record id(s). */\n add: (text: string, options?: AddOptions) => Promise<string[]>;\n /** Retrieve a token-budgeted context block for a query. */\n recall: (query: string, options?: RecallOptions) => Promise<RecallResult>;\n /** Rank stored entries by similarity to a query. */\n search: (query: string, options?: SearchOptions) => Promise<MemorySearchResult[]>;\n /** Get a single record by id. */\n get: (id: string) => Promise<MemoryRecord | undefined>;\n /** Delete a record by id. */\n remove: (id: string) => Promise<boolean>;\n /** Clear all memories in this namespace. */\n clear: () => Promise<void>;\n /** Number of stored records. */\n size: () => Promise<number>;\n /** Whether the embedding model is downloading. */\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n /** Whether memory is ready (the embedding model has loaded). */\n isReady: boolean;\n error: string | null;\n}\n\nexport function useMemory(options: UseMemoryOptions = {}): UseMemoryReturn {\n const { model, namespace = \"gerbil-memory\" } = options;\n const embedder = useEngine({ model, embedding: true, autoLoad: false });\n const memRef = useRef<MemoryLike | null>(null);\n const initRef = useRef<Promise<MemoryLike> | null>(null);\n const [isReady, setIsReady] = useState(false);\n\n // Lazily build the Memory facade on first use: load the embedding model, then\n // wire it to an IndexedDB store. Imported from submodules (not the barrel) so\n // the Node-only file store is never pulled into the browser bundle.\n const ensure = useCallback((): Promise<MemoryLike> => {\n if (memRef.current) return Promise.resolve(memRef.current);\n if (initRef.current) return initRef.current;\n initRef.current = (async () => {\n if (!embedder.isReady) await embedder.load();\n const [{ createMemory }, { createIndexedDBStore }] = await Promise.all([\n import(\"../memory/memory.js\"),\n import(\"../memory/stores/indexeddb-store.js\"),\n ]);\n const mem = createMemory({\n embed: async (texts: string[]) => Promise.all(texts.map((t) => embedder.embed(t))),\n store: createIndexedDBStore({ dbName: namespace }),\n }) as unknown as MemoryLike;\n memRef.current = mem;\n setIsReady(true);\n return mem;\n })();\n return initRef.current;\n }, [embedder, namespace]);\n\n const add = useCallback(\n async (text: string, opts?: AddOptions) => (await ensure()).add(text, opts),\n [ensure],\n );\n const recall = useCallback(\n async (query: string, opts?: RecallOptions) => (await ensure()).recall(query, opts),\n [ensure],\n );\n const search = useCallback(\n async (query: string, opts?: SearchOptions) => (await ensure()).search(query, opts),\n [ensure],\n );\n const get = useCallback(async (id: string) => (await ensure()).get(id), [ensure]);\n const remove = useCallback(async (id: string) => (await ensure()).delete(id), [ensure]);\n const clear = useCallback(async () => (await ensure()).clear(), [ensure]);\n const size = useCallback(async () => (await ensure()).size(), [ensure]);\n\n return {\n add,\n recall,\n search,\n get,\n remove,\n clear,\n size,\n isLoading: embedder.isLoading,\n loadingProgress: embedder.loadingProgress,\n isReady,\n error: embedder.error,\n };\n}\n","/**\n * Per-modality convenience hooks built on {@link useEngine}.\n *\n * `useEngine` is the general/advanced hook (it can do text, vision, and\n * embeddings via options). These wrappers give each modality a focused,\n * self-documenting surface so app code reads cleanly:\n *\n * ```tsx\n * import { useText, useVision, useEmbedding } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { complete } = useText(); // text generation\n * const { describeImage } = useVision(); // image → text\n * const { embed, similarity } = useEmbedding(); // text → vector\n * ```\n *\n * They share the same engine registry as `useEngine`, so requesting the same\n * model from several places loads it once.\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n type ChatMessage,\n type CompleteOptions,\n type EngineErrorKind,\n type GenerateObjectOptions,\n type UseEngineOptions,\n useEngine,\n} from \"./use-engine.js\";\n\n/** Options common to the modality hooks (the capability flag is set for you). */\nexport type ModalityOptions = Omit<UseEngineOptions, \"enableVision\" | \"embedding\">;\n\nexport interface UseTextReturn {\n complete: (prompt: string, options?: CompleteOptions) => Promise<string>;\n completion: string;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isGenerating: boolean;\n tps: number | null;\n error: string | null;\n errorKind: EngineErrorKind | null;\n isReady: boolean;\n load: () => Promise<void>;\n stop: () => void;\n dispose: () => void;\n}\n\n/** Text generation. */\nexport function useText(options: ModalityOptions = {}): UseTextReturn {\n const e = useEngine(options);\n return {\n complete: e.complete,\n completion: e.completion,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n isGenerating: e.isGenerating,\n tps: e.tps,\n error: e.error,\n errorKind: e.errorKind,\n isReady: e.isReady,\n load: e.load,\n stop: e.stop,\n dispose: e.dispose,\n };\n}\n\nexport interface UseObjectReturn<T = unknown> {\n /** The last successfully parsed + validated object (null until one is produced). */\n object: T | null;\n /**\n * Generate, parse JSON, validate against `schema`, and retry until valid.\n * Loads the model first if needed. Returns the parsed object.\n */\n generate: (prompt: string, options?: GenerateObjectOptions) => Promise<T>;\n /** Attempts the last `generate` call took (1 = first try). */\n attempts: number;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isGenerating: boolean;\n error: string | null;\n errorKind: EngineErrorKind | null;\n isReady: boolean;\n load: () => Promise<void>;\n stop: () => void;\n dispose: () => void;\n}\n\n/**\n * Structured-output generation — generate, parse JSON, validate, and RETRY\n * until valid. On-device tokens are free, so re-rolling malformed JSON is cheap.\n *\n * ```tsx\n * const { object, generate, isGenerating } = useObject<{ name: string; age: number }>();\n * await generate('Extract {name, age} from: \"I am Sarah, 28\"', {\n * schema: { required: [\"name\", \"age\"] },\n * });\n * // object === { name: \"Sarah\", age: 28 }\n * ```\n */\nexport function useObject<T = unknown>(options: ModalityOptions = {}): UseObjectReturn<T> {\n const e = useEngine(options);\n const [object, setObject] = useState<T | null>(null);\n\n const generate = useCallback(\n async (prompt: string, opts?: GenerateObjectOptions): Promise<T> => {\n if (!e.isReady) await e.load();\n const result = await e.generateObject<T>(prompt, opts);\n setObject(result.object);\n return result.object;\n },\n [e],\n );\n\n return {\n object,\n generate,\n attempts: e.attempts,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n isGenerating: e.isGenerating,\n error: e.error,\n errorKind: e.errorKind,\n isReady: e.isReady,\n load: e.load,\n stop: e.stop,\n dispose: e.dispose,\n };\n}\n\nexport interface UseVisionReturn {\n describeImage: UseEngineReturnLike[\"describeImage\"];\n completion: string;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isGenerating: boolean;\n tps: number | null;\n error: string | null;\n errorKind: EngineErrorKind | null;\n isReady: boolean;\n load: () => Promise<void>;\n stop: () => void;\n dispose: () => void;\n}\n\n// Local alias so the return types can reference describeImage/embed/similarity\n// signatures without re-declaring them.\ntype UseEngineReturnLike = ReturnType<typeof useEngine>;\n\n/** Image understanding (image in → text out). Builds the vision tower. */\nexport function useVision(options: ModalityOptions = {}): UseVisionReturn {\n const e = useEngine({ ...options, enableVision: true });\n return {\n describeImage: e.describeImage,\n completion: e.completion,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n isGenerating: e.isGenerating,\n tps: e.tps,\n error: e.error,\n errorKind: e.errorKind,\n isReady: e.isReady,\n load: e.load,\n stop: e.stop,\n dispose: e.dispose,\n };\n}\n\nexport interface UseEmbeddingReturn {\n embed: UseEngineReturnLike[\"embed\"];\n similarity: UseEngineReturnLike[\"similarity\"];\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n error: string | null;\n errorKind: EngineErrorKind | null;\n isReady: boolean;\n load: () => Promise<void>;\n dispose: () => void;\n}\n\n/** Text embeddings + similarity. */\nexport function useEmbedding(options: ModalityOptions = {}): UseEmbeddingReturn {\n const e = useEngine({ ...options, embedding: true });\n return {\n embed: e.embed,\n similarity: e.similarity,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n error: e.error,\n errorKind: e.errorKind,\n isReady: e.isReady,\n load: e.load,\n dispose: e.dispose,\n };\n}\n\nexport interface UseChatOptions extends ModalityOptions {\n /** System prompt prepended to every turn. */\n system?: string;\n}\n\n/** Chat lifecycle status (mirrors the Vercel AI SDK's `useChat` status). */\nexport type ChatStatus = \"ready\" | \"submitted\" | \"streaming\" | \"error\";\n\nexport interface UseChatReturn {\n /** The running conversation (user + assistant turns). */\n messages: ChatMessage[];\n /** Send a user message; the assistant reply streams in and is returned. */\n send: (text: string, options?: CompleteOptions) => Promise<string>;\n /** Alias of `send` (AI SDK-compatible name). */\n sendMessage: (text: string, options?: CompleteOptions) => Promise<string>;\n /** Re-run the last user turn (drops the previous assistant reply). */\n regenerate: (options?: CompleteOptions) => Promise<string>;\n /** Replace the conversation (value or updater). */\n setMessages: (next: ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[])) => void;\n /** Clear the conversation. */\n clear: () => void;\n /** Coarse lifecycle status: ready → submitted → streaming (→ error). */\n status: ChatStatus;\n isGenerating: boolean;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isReady: boolean;\n tps: number | null;\n error: string | null;\n errorKind: EngineErrorKind | null;\n stop: () => void;\n load: () => Promise<void>;\n}\n\n/**\n * Conversational chat hook — manages the message list and streams replies.\n * Multi-turn context is handled for you (the full history is sent each turn).\n *\n * ```tsx\n * const { messages, send, isGenerating } = useChat();\n * <button onClick={() => send(\"Hello!\")}>Send</button>\n * ```\n */\nexport function useChat(options: UseChatOptions = {}): UseChatReturn {\n const { system, ...engineOptions } = options;\n const e = useEngine(engineOptions);\n\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n // A ref mirror so send() always reads the latest history without a stale closure.\n const messagesRef = useRef<ChatMessage[]>([]);\n messagesRef.current = messages;\n\n // Stream the live completion into the trailing assistant turn as it generates.\n useEffect(() => {\n if (!e.isGenerating) return;\n setMessages((prev) => {\n if (prev.length === 0 || prev[prev.length - 1].role !== \"assistant\") return prev;\n const copy = prev.slice();\n copy[copy.length - 1] = { role: \"assistant\", content: e.completion };\n return copy;\n });\n }, [e.completion, e.isGenerating]);\n\n // Run a completion over the given history, streaming into the trailing\n // assistant turn, and return the final reply text.\n const run = useCallback(\n async (history: ChatMessage[], opts: CompleteOptions): Promise<string> => {\n setMessages([...history, { role: \"assistant\", content: \"\" }]);\n if (!e.isReady) await e.load();\n const turns: ChatMessage[] = system\n ? [{ role: \"system\", content: system }, ...history]\n : history;\n const full = await e.complete(turns, { ...opts, system: opts.system ?? system });\n setMessages((prev) => {\n if (prev.length === 0) return prev;\n const copy = prev.slice();\n copy[copy.length - 1] = { role: \"assistant\", content: full };\n return copy;\n });\n return full;\n },\n [e, system],\n );\n\n const send = useCallback(\n async (text: string, opts: CompleteOptions = {}): Promise<string> => {\n if (!text.trim() || e.isGenerating) return \"\";\n return run([...messagesRef.current, { role: \"user\", content: text }], opts);\n },\n [e.isGenerating, run],\n );\n\n const regenerate = useCallback(\n async (opts: CompleteOptions = {}): Promise<string> => {\n if (e.isGenerating) return \"\";\n // Drop trailing assistant turns; re-run from the last user message.\n const msgs = messagesRef.current.slice();\n while (msgs.length > 0 && msgs[msgs.length - 1].role === \"assistant\") msgs.pop();\n if (msgs.length === 0) return \"\";\n return run(msgs, opts);\n },\n [e.isGenerating, run],\n );\n\n const setMessagesPublic = useCallback(\n (next: ChatMessage[] | ((prev: ChatMessage[]) => ChatMessage[])) => setMessages(next),\n [],\n );\n const clear = useCallback(() => setMessages([]), []);\n\n const status: ChatStatus = e.error\n ? \"error\"\n : e.isGenerating\n ? e.completion.length === 0\n ? \"submitted\"\n : \"streaming\"\n : \"ready\";\n\n return {\n messages,\n send,\n sendMessage: send,\n regenerate,\n setMessages: setMessagesPublic,\n clear,\n status,\n isGenerating: e.isGenerating,\n isLoading: e.isLoading,\n loadingProgress: e.loadingProgress,\n isReady: e.isReady,\n tps: e.tps,\n error: e.error,\n errorKind: e.errorKind,\n stop: e.stop,\n load: e.load,\n };\n}\n\nexport interface UseCompletionReturn {\n /** The streamed completion text. */\n completion: string;\n /** Generate a completion for a prompt (loads the model if needed). */\n complete: (prompt: string, options?: CompleteOptions) => Promise<string>;\n /** Controlled input value (AI SDK-style). */\n input: string;\n setInput: (value: string) => void;\n handleInputChange: (e: { target: { value: string } }) => void;\n handleSubmit: (e?: { preventDefault?: () => void }) => void;\n isLoading: boolean;\n isReady: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n stop: () => void;\n error: string | null;\n load: () => Promise<void>;\n}\n\n/**\n * Single-prompt streaming completion with built-in input state — a near\n * drop-in for the Vercel AI SDK's `useCompletion`, running on-device.\n */\nexport function useCompletion(options: ModalityOptions = {}): UseCompletionReturn {\n const t = useText(options);\n const [input, setInput] = useState(\"\");\n\n const complete = useCallback(\n async (prompt: string, opts?: CompleteOptions): Promise<string> => {\n if (!t.isReady) await t.load();\n return t.complete(prompt, opts);\n },\n [t],\n );\n\n const handleInputChange = useCallback(\n (e: { target: { value: string } }) => setInput(e.target.value),\n [],\n );\n const handleSubmit = useCallback(\n (e?: { preventDefault?: () => void }) => {\n e?.preventDefault?.();\n const value = input;\n if (!value.trim()) return;\n setInput(\"\");\n void complete(value);\n },\n [input, complete],\n );\n\n return {\n completion: t.completion,\n complete,\n input,\n setInput,\n handleInputChange,\n handleSubmit,\n isLoading: t.isGenerating,\n isReady: t.isReady,\n loadingProgress: t.loadingProgress,\n stop: t.stop,\n error: t.error,\n load: t.load,\n };\n}\n","/**\n * React hook for native speech-to-text in the browser.\n *\n * Wraps `MoonshineSTT` — raw 16 kHz mono PCM in, transcript out (encoder-decoder\n * ASR, no streaming/partial API). This hook captures mic audio between\n * start/stop, resamples it to 16 kHz mono, and runs a single transcribe() on the\n * finalized utterance. The GPU engine is dynamically imported so it stays out of\n * the main bundle until STT is actually used.\n *\n * @example\n * ```tsx\n * import { useSTT } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { startRecording, stopRecording, transcript, isRecording } = useSTT();\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { DEFAULT_MODELS } from \"../gpu/defaults.js\";\n// STATIC engine import (NOT dynamic) — see ./engine.ts (iOS bundler-chunk hang).\nimport { MoonshineSTT } from \"./engine.js\";\n\ntype MoonshineSTTType = import(\"../gpu/index.js\").MoonshineSTT;\n\nconst MOONSHINE_SAMPLE_RATE = 16_000;\n\nexport interface UseSTTOptions {\n /** HF repo for the STT model (default: the built-in Moonshine). */\n repo?: string;\n /** Auto-load the model on mount (default: false — loads on first record). */\n autoLoad?: boolean;\n onReady?: () => void;\n onError?: (error: Error) => void;\n /** Called when a finalized utterance contains no speech (silence/noise). */\n onNoSpeech?: () => void;\n}\n\nexport interface UseSTTReturn {\n load: () => Promise<void>;\n startRecording: () => Promise<void>;\n stopRecording: () => Promise<void>;\n dispose: () => void;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isReady: boolean;\n isRecording: boolean;\n isTranscribing: boolean;\n transcript: string;\n audioSeconds: number | null;\n /** True when the last finalized utterance contained no speech. */\n noSpeech: boolean;\n error: string | null;\n}\n\n/** Downmix to mono and linearly resample a Float32 buffer to 16 kHz. */\nfunction toMono16k(channels: Float32Array[], inputRate: number): Float32Array {\n const inLen = channels[0]?.length ?? 0;\n const mono = new Float32Array(inLen);\n for (const ch of channels) {\n for (let i = 0; i < inLen; i++) mono[i] += ch[i] / channels.length;\n }\n if (inputRate === MOONSHINE_SAMPLE_RATE) return mono;\n const ratio = MOONSHINE_SAMPLE_RATE / inputRate;\n const outLen = Math.max(0, Math.floor(inLen * ratio));\n const out = new Float32Array(outLen);\n for (let i = 0; i < outLen; i++) {\n const srcPos = i / ratio;\n const i0 = Math.floor(srcPos);\n const i1 = Math.min(i0 + 1, inLen - 1);\n const frac = srcPos - i0;\n out[i] = mono[i0] * (1 - frac) + mono[i1] * frac;\n }\n return out;\n}\n\nexport function useSTT(options: UseSTTOptions = {}): UseSTTReturn {\n const { repo = DEFAULT_MODELS.stt, autoLoad = false, onReady, onError, onNoSpeech } = options;\n\n const sttRef = useRef<MoonshineSTTType | null>(null);\n const loadingRef = useRef(false);\n const mediaStreamRef = useRef<MediaStream | null>(null);\n const audioCtxRef = useRef<AudioContext | null>(null);\n const sourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const processorRef = useRef<ScriptProcessorNode | null>(null);\n const chunksRef = useRef<Float32Array[]>([]);\n const sampleRateRef = useRef<number>(MOONSHINE_SAMPLE_RATE);\n\n const [isLoading, setIsLoading] = useState(false);\n const [loadingProgress, setLoadingProgress] = useState<{\n status: string;\n progress?: number;\n } | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [isRecording, setIsRecording] = useState(false);\n const [isTranscribing, setIsTranscribing] = useState(false);\n const [transcript, setTranscript] = useState(\"\");\n const [audioSeconds, setAudioSeconds] = useState<number | null>(null);\n const [noSpeech, setNoSpeech] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n if (sttRef.current || loadingRef.current) return;\n loadingRef.current = true;\n\n if (typeof navigator === \"undefined\" || !(\"gpu\" in navigator)) {\n loadingRef.current = false;\n const err = new Error(\n \"WebGPU is not available in this browser. Native speech-to-text requires Chrome/Edge 113+, Firefox 141+, or Safari 26+.\",\n );\n setError(err.message);\n onError?.(err);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n setLoadingProgress({ status: \"Initializing speech-to-text...\" });\n\n try {\n const stt = await MoonshineSTT.create({\n repo,\n onProgress: (loaded: number, total: number, message: string) => {\n setLoadingProgress({\n status: message,\n progress: total > 0 ? Math.round((loaded / total) * 100) : undefined,\n });\n },\n });\n sttRef.current = stt;\n setIsReady(true);\n setIsLoading(false);\n setLoadingProgress(null);\n onReady?.();\n } catch (e) {\n loadingRef.current = false;\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err.message);\n setIsLoading(false);\n setLoadingProgress(null);\n onError?.(err);\n }\n }, [repo, onReady, onError]);\n\n const teardownCapture = useCallback(() => {\n processorRef.current?.disconnect();\n sourceRef.current?.disconnect();\n if (audioCtxRef.current && audioCtxRef.current.state !== \"closed\") {\n void audioCtxRef.current.close();\n }\n for (const t of mediaStreamRef.current?.getTracks() ?? []) t.stop();\n processorRef.current = null;\n sourceRef.current = null;\n audioCtxRef.current = null;\n mediaStreamRef.current = null;\n }, []);\n\n const startRecording = useCallback(async () => {\n if (isRecording) return;\n if (!sttRef.current) await load();\n if (!sttRef.current) return; // load failed (error already set)\n\n setTranscript(\"\");\n setAudioSeconds(null);\n setNoSpeech(false);\n setError(null);\n chunksRef.current = [];\n\n let stream: MediaStream;\n try {\n stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n const name = (err as { name?: string }).name;\n if (name === \"NotAllowedError\" || name === \"SecurityError\") {\n setError(\"Microphone access denied. Allow mic access for this site and try again.\");\n } else if (name === \"NotFoundError\" || name === \"DevicesNotFoundError\") {\n setError(\"No microphone found. Connect a mic and try again.\");\n } else {\n setError(err.message);\n }\n onError?.(err);\n return;\n }\n mediaStreamRef.current = stream;\n\n const AudioCtx =\n (window as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext })\n .AudioContext ??\n (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioCtx) return;\n const ctx = new AudioCtx();\n audioCtxRef.current = ctx;\n sampleRateRef.current = ctx.sampleRate;\n\n const source = ctx.createMediaStreamSource(stream);\n sourceRef.current = source;\n\n // ScriptProcessor is deprecated but ubiquitous (incl. iOS Safari) and gives\n // raw Float32 PCM frames directly — exactly what Moonshine wants.\n const processor = ctx.createScriptProcessor(4096, 1, 1);\n processorRef.current = processor;\n processor.onaudioprocess = (ev) => {\n const input = ev.inputBuffer.getChannelData(0);\n chunksRef.current.push(new Float32Array(input));\n };\n source.connect(processor);\n processor.connect(ctx.destination);\n\n setIsRecording(true);\n }, [isRecording, load, onError]);\n\n const stopRecording = useCallback(async () => {\n if (!isRecording) return;\n setIsRecording(false);\n\n const inputRate = sampleRateRef.current;\n const captured = chunksRef.current;\n chunksRef.current = [];\n teardownCapture();\n\n const total = captured.reduce((n, c) => n + c.length, 0);\n const joined = new Float32Array(total);\n let off = 0;\n for (const c of captured) {\n joined.set(c, off);\n off += c.length;\n }\n const pcm = toMono16k([joined], inputRate);\n\n // Moonshine needs >= conv1 kernel size (127 samples).\n if (pcm.length < 127) {\n setError(\"Recording was too short. Hold the mic a moment longer.\");\n return;\n }\n\n setIsTranscribing(true);\n setError(null);\n try {\n const result = await (\n sttRef.current as MoonshineSTTType & {\n transcribe: (p: Float32Array) => Promise<{\n text: string;\n audioSeconds: number;\n noSpeech: boolean;\n speechRms: number;\n }>;\n }\n ).transcribe(pcm);\n setAudioSeconds(result.audioSeconds);\n setNoSpeech(result.noSpeech);\n setTranscript(result.text);\n if (result.noSpeech) {\n onNoSpeech?.();\n }\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err.message);\n onError?.(err);\n } finally {\n setIsTranscribing(false);\n }\n }, [isRecording, teardownCapture, onError, onNoSpeech]);\n\n const dispose = useCallback(() => {\n teardownCapture();\n if (sttRef.current) {\n (sttRef.current as MoonshineSTTType & { destroy?: () => void }).destroy?.();\n sttRef.current = null;\n loadingRef.current = false;\n setIsReady(false);\n }\n }, [teardownCapture]);\n\n useEffect(() => {\n if (autoLoad) void load();\n return () => {\n teardownCapture();\n if (sttRef.current) {\n (sttRef.current as MoonshineSTTType & { destroy?: () => void }).destroy?.();\n sttRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return {\n load,\n startRecording,\n stopRecording,\n dispose,\n isLoading,\n loadingProgress,\n isReady,\n isRecording,\n isTranscribing,\n transcript,\n audioSeconds,\n noSpeech,\n error,\n };\n}\n","/**\n * React hook for native text-to-speech in the browser.\n *\n * Wraps the engine's `speak()` (Kani-TTS-2) — the codec-LM backbone emits\n * NanoCodec audio tokens, the NanoCodec decoder turns them into 22.05 kHz mono\n * PCM, and this hook plays it through the Web Audio API (and keeps the clip for\n * instant replay). The GPU engine is dynamically imported so it stays out of the\n * main bundle until TTS is actually used.\n *\n * @example\n * ```tsx\n * import { useTTS } from \"@tryhamster/gerbil/gpu/hooks\";\n *\n * const { speak, isSynthesizing, isPlaying } = useTTS();\n * <button onClick={() => speak(\"Hello from on-device TTS.\")}>Speak</button>\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { DEFAULT_MODELS } from \"../gpu/defaults.js\";\n// STATIC engine import (NOT dynamic) — see ./engine.ts (iOS bundler-chunk hang).\nimport { WebGPUEngine } from \"./engine.js\";\n\ntype WebGPUEngineType = import(\"../gpu/index.js\").WebGPUEngine;\n\nconst KANI_SAMPLE_RATE = 22_050;\n\n/**\n * Built-in voices. Kani-TTS-2-en takes an `en_us`-style language tag prepended\n * to the text; the English checkpoint ships the US-English voice.\n */\nexport const KANI_VOICES = [{ value: \"en_us\", label: \"English (US)\" }] as const;\n\nexport type KaniVoice = (typeof KANI_VOICES)[number][\"value\"];\n\nexport interface SpeakOptions {\n /** Language/accent tag, e.g. \"en_us\". Prepended as \"{tag}: {text}\". */\n voice?: string;\n /** Sampling temperature (default 1.0). Higher = more expressive/varied. */\n temperature?: number;\n /** Top-p nucleus threshold (default 0.95). */\n topP?: number;\n /** Repetition penalty (default 1.1). */\n repetitionPenalty?: number;\n}\n\nexport interface UseTTSOptions {\n /** HF repo for the TTS model (default: the built-in Kani-TTS-2). */\n repo?: string;\n /** Auto-load the model on mount (default: false — loads on first speak). */\n autoLoad?: boolean;\n onReady?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface UseTTSReturn {\n load: () => Promise<void>;\n /** Synthesize + play `text`. Lazily loads the model on first call. */\n speak: (text: string, options?: SpeakOptions) => Promise<void>;\n /** Replay the most recently synthesized clip (no re-synthesis). */\n replay: () => Promise<void>;\n /** Stop any in-progress playback. */\n stop: () => void;\n dispose: () => void;\n isLoading: boolean;\n loadingProgress: { status: string; progress?: number } | null;\n isReady: boolean;\n /** Synthesizing PCM (running the codec-LM + NanoCodec). */\n isSynthesizing: boolean;\n /** Audio is currently playing. */\n isPlaying: boolean;\n /** True once a clip has been synthesized and is available for replay. */\n hasAudio: boolean;\n /** Duration (seconds) of the last synthesized clip. */\n audioSeconds: number | null;\n /** Real-time factor of the last synthesis (audio-sec per wall-sec). */\n rtf: number | null;\n error: string | null;\n}\n\n/** Build an AudioBuffer from mono Float32 PCM at the given sample rate. */\nfunction pcmToAudioBuffer(ctx: AudioContext, pcm: Float32Array, sampleRate: number): AudioBuffer {\n const buffer = ctx.createBuffer(1, pcm.length, sampleRate);\n buffer.getChannelData(0).set(pcm);\n return buffer;\n}\n\nexport function useTTS(options: UseTTSOptions = {}): UseTTSReturn {\n const { repo = DEFAULT_MODELS.tts, autoLoad = false, onReady, onError } = options;\n\n const engineRef = useRef<WebGPUEngineType | null>(null);\n const loadingRef = useRef(false);\n const audioCtxRef = useRef<AudioContext | null>(null);\n const sourceRef = useRef<AudioBufferSourceNode | null>(null);\n const bufferRef = useRef<AudioBuffer | null>(null);\n\n const [isLoading, setIsLoading] = useState(false);\n const [loadingProgress, setLoadingProgress] = useState<{\n status: string;\n progress?: number;\n } | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [isSynthesizing, setIsSynthesizing] = useState(false);\n const [isPlaying, setIsPlaying] = useState(false);\n const [hasAudio, setHasAudio] = useState(false);\n const [audioSeconds, setAudioSeconds] = useState<number | null>(null);\n const [rtf, setRtf] = useState<number | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n if (engineRef.current || loadingRef.current) return;\n loadingRef.current = true;\n\n if (typeof navigator === \"undefined\" || !(\"gpu\" in navigator)) {\n loadingRef.current = false;\n const err = new Error(\n \"WebGPU is not available in this browser. Native text-to-speech requires Chrome/Edge 113+, Firefox 141+, or Safari 26+.\",\n );\n setError(err.message);\n onError?.(err);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n setLoadingProgress({ status: \"Initializing TTS...\" });\n\n try {\n const engine = await WebGPUEngine.create({\n repo,\n onProgress: (loaded: number, total: number, message: string) => {\n setLoadingProgress({\n status: message,\n progress: total > 0 ? Math.round((loaded / total) * 100) : undefined,\n });\n },\n });\n engineRef.current = engine;\n setIsReady(true);\n setIsLoading(false);\n setLoadingProgress(null);\n onReady?.();\n } catch (e) {\n loadingRef.current = false;\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err.message);\n setIsLoading(false);\n setLoadingProgress(null);\n onError?.(err);\n }\n }, [repo, onReady, onError]);\n\n const stop = useCallback(() => {\n if (sourceRef.current) {\n try {\n sourceRef.current.onended = null;\n sourceRef.current.stop();\n } catch {\n // Already stopped.\n }\n sourceRef.current = null;\n }\n setIsPlaying(false);\n }, []);\n\n const playBuffer = useCallback(async () => {\n const buffer = bufferRef.current;\n if (!buffer) return;\n const AudioCtx =\n (window as { AudioContext?: typeof AudioContext; webkitAudioContext?: typeof AudioContext })\n .AudioContext ??\n (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioCtx) return;\n if (!audioCtxRef.current || audioCtxRef.current.state === \"closed\") {\n audioCtxRef.current = new AudioCtx();\n }\n const ctx = audioCtxRef.current;\n if (!ctx) return;\n if (ctx.state === \"suspended\") await ctx.resume();\n stop();\n const source = ctx.createBufferSource();\n source.buffer = buffer;\n source.connect(ctx.destination);\n source.onended = () => {\n setIsPlaying(false);\n sourceRef.current = null;\n };\n sourceRef.current = source;\n setIsPlaying(true);\n source.start();\n }, [stop]);\n\n const speak = useCallback(\n async (text: string, opts: SpeakOptions = {}): Promise<void> => {\n if (!text.trim()) return;\n // Unlock audio WITHIN the user gesture, BEFORE any await. speak() is called\n // straight from a click, but model load + synthesis take seconds; an\n // AudioContext first created/resumed after those awaits starts suspended on\n // iOS (and under Chrome's autoplay policy) and never makes sound. Create,\n // resume, and play a 1-sample silent buffer now, while the gesture is live.\n {\n const AudioCtx =\n (\n window as {\n AudioContext?: typeof AudioContext;\n webkitAudioContext?: typeof AudioContext;\n }\n ).AudioContext ??\n (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (AudioCtx) {\n if (!audioCtxRef.current || audioCtxRef.current.state === \"closed\") {\n audioCtxRef.current = new AudioCtx();\n }\n const ctx = audioCtxRef.current;\n if (ctx.state === \"suspended\") void ctx.resume();\n try {\n const warm = ctx.createBufferSource();\n warm.buffer = ctx.createBuffer(1, 1, ctx.sampleRate);\n warm.connect(ctx.destination);\n warm.start(0);\n } catch {\n /* warm-up best-effort */\n }\n }\n }\n if (!engineRef.current) await load();\n const engine = engineRef.current as\n | (WebGPUEngineType & {\n speak: (\n t: string,\n o: unknown,\n ) => Promise<{ pcm: Float32Array; sampleRate: number; audioSeconds: number }>;\n })\n | null;\n if (!engine) return; // load failed (error already set)\n\n setIsSynthesizing(true);\n setError(null);\n try {\n const t0 = performance.now();\n const {\n pcm,\n sampleRate,\n audioSeconds: secs,\n } = await engine.speak(text, {\n languageTag: opts.voice ?? \"en_us\",\n temperature: opts.temperature ?? 1.0,\n topP: opts.topP ?? 0.95,\n repetitionPenalty: opts.repetitionPenalty ?? 1.1,\n });\n const wall = (performance.now() - t0) / 1000;\n const AudioCtx =\n (\n window as {\n AudioContext?: typeof AudioContext;\n webkitAudioContext?: typeof AudioContext;\n }\n ).AudioContext ??\n (window as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (AudioCtx && (!audioCtxRef.current || audioCtxRef.current.state === \"closed\")) {\n audioCtxRef.current = new AudioCtx();\n }\n if (audioCtxRef.current) {\n bufferRef.current = pcmToAudioBuffer(\n audioCtxRef.current,\n pcm,\n sampleRate ?? KANI_SAMPLE_RATE,\n );\n }\n setHasAudio(true);\n setAudioSeconds(secs);\n setRtf(wall > 0 ? secs / wall : null);\n setIsSynthesizing(false);\n await playBuffer();\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n setError(err.message);\n setIsSynthesizing(false);\n onError?.(err);\n }\n },\n [load, playBuffer, onError],\n );\n\n const replay = useCallback(async () => {\n if (!bufferRef.current) return;\n await playBuffer();\n }, [playBuffer]);\n\n const dispose = useCallback(() => {\n stop();\n if (audioCtxRef.current && audioCtxRef.current.state !== \"closed\") {\n void audioCtxRef.current.close();\n }\n audioCtxRef.current = null;\n bufferRef.current = null;\n if (engineRef.current) {\n engineRef.current.destroy?.();\n engineRef.current = null;\n loadingRef.current = false;\n setIsReady(false);\n }\n }, [stop]);\n\n useEffect(() => {\n if (autoLoad) void load();\n return () => {\n if (sourceRef.current) {\n try {\n sourceRef.current.onended = null;\n sourceRef.current.stop();\n } catch {\n // Already stopped.\n }\n sourceRef.current = null;\n }\n if (audioCtxRef.current && audioCtxRef.current.state !== \"closed\") {\n void audioCtxRef.current.close();\n }\n if (engineRef.current) {\n engineRef.current.destroy?.();\n engineRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return {\n load,\n speak,\n replay,\n stop,\n dispose,\n isLoading,\n loadingProgress,\n isReady,\n isSynthesizing,\n isPlaying,\n hasAudio,\n audioSeconds,\n rtf,\n error,\n };\n}\n","/**\n * React hook for a fully on-device voice assistant: speak to it, it transcribes,\n * thinks, and speaks back — no cloud, no API keys. Composes {@link useSTT},\n * {@link useChat}, and {@link useTTS} into one flow.\n *\n * ```tsx\n * import { useVoiceChat } from \"@tryhamster/gerbil/hooks\";\n *\n * const vc = useVoiceChat();\n * <button onMouseDown={vc.start} onMouseUp={vc.stop}>\n * {vc.isListening ? \"Listening…\" : \"Hold to talk\"}\n * </button>\n * // vc.messages renders the conversation; replies are spoken automatically.\n * ```\n *\n * This is Gerbil-unique — a private, offline voice loop the cloud SDKs can't do.\n */\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport type { ChatMessage } from \"./use-engine.js\";\nimport { type UseChatOptions, useChat } from \"./use-modalities.js\";\nimport { useSTT } from \"./use-stt.js\";\nimport { useTTS } from \"./use-tts.js\";\n\nexport interface UseVoiceChatOptions extends UseChatOptions {\n /** Speech-to-text model repo (default: built-in Moonshine). */\n sttModel?: string;\n /** Text-to-speech model repo (default: built-in Kani-TTS-2). */\n ttsModel?: string;\n /** Voice for spoken replies (e.g. \"en_us\"). */\n voice?: string;\n /** Speak replies aloud (default: true). Set false for text-only. */\n speak?: boolean;\n}\n\nexport interface UseVoiceChatReturn {\n /** The running conversation. */\n messages: ChatMessage[];\n /** Start listening (opens the mic). */\n start: () => Promise<void>;\n /** Stop listening → transcribe → reply → speak. */\n stop: () => Promise<void>;\n /** Stop playback of the current spoken reply. */\n stopSpeaking: () => void;\n /** Clear the conversation. */\n clear: () => void;\n /** Mic is open and capturing. */\n isListening: boolean;\n /** Transcribing the captured audio. */\n isTranscribing: boolean;\n /** The model is generating a reply. */\n isThinking: boolean;\n /** A reply is being synthesized or played. */\n isSpeaking: boolean;\n /** The most recent transcribed user utterance. */\n transcript: string;\n /** Any model is still downloading. */\n isLoading: boolean;\n /** The chat model is ready. */\n isReady: boolean;\n error: string | null;\n}\n\nexport function useVoiceChat(options: UseVoiceChatOptions = {}): UseVoiceChatReturn {\n const { sttModel, ttsModel, voice, speak = true, ...chatOptions } = options;\n const stt = useSTT({ repo: sttModel });\n const chat = useChat(chatOptions);\n const tts = useTTS({ repo: ttsModel });\n\n // When a transcript lands (recording stopped, transcription done), feed it to\n // the model and speak the reply. A ref guards against re-processing the same\n // utterance on re-render.\n const processedRef = useRef(\"\");\n useEffect(() => {\n const text = stt.transcript.trim();\n if (!text || stt.isTranscribing || text === processedRef.current) return;\n processedRef.current = text;\n (async () => {\n const reply = await chat.send(text);\n if (speak && reply.trim()) await tts.speak(reply, voice ? { voice } : undefined);\n })();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [stt.transcript, stt.isTranscribing]);\n\n const start = useCallback(() => stt.startRecording(), [stt]);\n const stop = useCallback(() => stt.stopRecording(), [stt]);\n\n return {\n messages: chat.messages,\n start,\n stop,\n stopSpeaking: tts.stop,\n clear: chat.clear,\n isListening: stt.isRecording,\n isTranscribing: stt.isTranscribing,\n isThinking: chat.isGenerating,\n isSpeaking: tts.isSynthesizing || tts.isPlaying,\n transcript: stt.transcript,\n isLoading: stt.isLoading || chat.isLoading || tts.isLoading,\n isReady: chat.isReady,\n error: stt.error ?? chat.error ?? tts.error,\n };\n}\n"],"mappings":"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"}
|
package/dist/gpu/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as buildGemma4PoolMatrix, A as generateMoonshineEncoderGraph, B as generateGemma4VisionGraph, C as KaniTTSOptions, Ct as KVDType, D as generateQwen3_5VisionGraph, E as Executor, Et as ModelCapabilities, F as generateNanoCodecDecoderGraph, G as GEMMA4_IMAGE_PROCESSOR, H as resolveGemma4VisionInfo, I as parseKaniConfig, J as ImageProcessorConfig, K as Gemma4VisionGridConfig, L as Gemma4VisionGraphInfo, M as parseMoonshineConfig, N as audioTokensToCodes, O as MOONSHINE_REMAINING_WORK, P as generateKaniTtsGraph, Q as VisionPositionTensors, R as dequantizeGemma4VisionProjection, S as KaniTTS, St as GraphDType, T as SpeakResult, Tt as ModelArchConfig, U as DEFAULT_MODELS, V as patchGemma4VisionClips, W as resolveDefaultRepo, X as QWEN3_5_IMAGE_PROCESSOR, Y as PreprocessedImage, Z as VisionGridConfig, _ as MoonshineSTT, _t as loadModel, a as GenerateObjectOptions, at as buildPosEmbeds, b as TranscribeResult, bt as GPUDiagnosticResult, c as GenerateResult, ct as buildVisionPositionTensors, d as ObjectSchema, dt as preprocessImageGemma4, et as buildGemma4PosEmbeds, f as ObjectValidator, ft as smartResize, g as VisionInputs, gt as loadKaniTTS, h as VisionExecutor, ht as LoadedMoonshine, i as EncodeImageResult, it as buildMRoPEPositionIds, j as moonshineEncoderFrames, k as generateMoonshineDecoderGraph, l as IntegrityCheckEntry, lt as mropeFreqDims, m as WebGPUEngineOptions, mt as LoadedKaniTTS, n as AgentTool, nt as buildGemma4VisionPositionTensors, o as GenerateObjectResult, ot as buildPositionIds, p as WebGPUEngine, pt as SamplingParams, q as Gemma4VisionPositionTensors, r as EmbedOptions, rt as buildMRoPECosSin, s as GenerateOptions, st as buildRotaryCosSin, t as AgentStep, tt as buildGemma4RotaryCosSin, u as IntegrityCheckResult, ut as preprocessImage, v as MoonshineSTTOptions, vt as loadMoonshine, w as SpeakOptions, wt as KvMode, x as MoonshineEncoderExecutor, xt as initGPU, y as TranscribeOptions, yt as ChatMessage, z as dequantizeMLXProjection } from "../index-
|
|
1
|
+
import { $ as buildGemma4PoolMatrix, A as generateMoonshineEncoderGraph, B as generateGemma4VisionGraph, C as KaniTTSOptions, Ct as KVDType, D as generateQwen3_5VisionGraph, E as Executor, Et as ModelCapabilities, F as generateNanoCodecDecoderGraph, G as GEMMA4_IMAGE_PROCESSOR, H as resolveGemma4VisionInfo, I as parseKaniConfig, J as ImageProcessorConfig, K as Gemma4VisionGridConfig, L as Gemma4VisionGraphInfo, M as parseMoonshineConfig, N as audioTokensToCodes, O as MOONSHINE_REMAINING_WORK, P as generateKaniTtsGraph, Q as VisionPositionTensors, R as dequantizeGemma4VisionProjection, S as KaniTTS, St as GraphDType, T as SpeakResult, Tt as ModelArchConfig, U as DEFAULT_MODELS, V as patchGemma4VisionClips, W as resolveDefaultRepo, X as QWEN3_5_IMAGE_PROCESSOR, Y as PreprocessedImage, Z as VisionGridConfig, _ as MoonshineSTT, _t as loadModel, a as GenerateObjectOptions, at as buildPosEmbeds, b as TranscribeResult, bt as GPUDiagnosticResult, c as GenerateResult, ct as buildVisionPositionTensors, d as ObjectSchema, dt as preprocessImageGemma4, et as buildGemma4PosEmbeds, f as ObjectValidator, ft as smartResize, g as VisionInputs, gt as loadKaniTTS, h as VisionExecutor, ht as LoadedMoonshine, i as EncodeImageResult, it as buildMRoPEPositionIds, j as moonshineEncoderFrames, k as generateMoonshineDecoderGraph, l as IntegrityCheckEntry, lt as mropeFreqDims, m as WebGPUEngineOptions, mt as LoadedKaniTTS, n as AgentTool, nt as buildGemma4VisionPositionTensors, o as GenerateObjectResult, ot as buildPositionIds, p as WebGPUEngine, pt as SamplingParams, q as Gemma4VisionPositionTensors, r as EmbedOptions, rt as buildMRoPECosSin, s as GenerateOptions, st as buildRotaryCosSin, t as AgentStep, tt as buildGemma4RotaryCosSin, u as IntegrityCheckResult, ut as preprocessImage, v as MoonshineSTTOptions, vt as loadMoonshine, w as SpeakOptions, wt as KvMode, x as MoonshineEncoderExecutor, xt as initGPU, y as TranscribeOptions, yt as ChatMessage, z as dequantizeMLXProjection } from "../index-Nl40RdSs.mjs";
|
|
2
2
|
export { AgentStep, AgentTool, ChatMessage, DEFAULT_MODELS, EmbedOptions, EncodeImageResult, Executor, GEMMA4_IMAGE_PROCESSOR, GPUDiagnosticResult, Gemma4VisionGraphInfo, Gemma4VisionGridConfig, Gemma4VisionPositionTensors, GenerateObjectOptions, GenerateObjectResult, GenerateOptions, GenerateResult, GraphDType, ImageProcessorConfig, IntegrityCheckEntry, IntegrityCheckResult, KVDType, KaniTTS, KaniTTSOptions, KvMode, LoadedKaniTTS, LoadedMoonshine, MOONSHINE_REMAINING_WORK, ModelArchConfig, ModelCapabilities, MoonshineEncoderExecutor, MoonshineSTT, MoonshineSTTOptions, ObjectSchema, ObjectValidator, PreprocessedImage, QWEN3_5_IMAGE_PROCESSOR, SamplingParams, SpeakOptions, SpeakResult, TranscribeOptions, TranscribeResult, VisionExecutor, VisionGridConfig, VisionInputs, VisionPositionTensors, WebGPUEngine, WebGPUEngineOptions, audioTokensToCodes, buildGemma4PoolMatrix, buildGemma4PosEmbeds, buildGemma4RotaryCosSin, buildGemma4VisionPositionTensors, buildMRoPECosSin, buildMRoPEPositionIds, buildPosEmbeds, buildPositionIds, buildRotaryCosSin, buildVisionPositionTensors, dequantizeGemma4VisionProjection, dequantizeMLXProjection, generateGemma4VisionGraph, generateKaniTtsGraph, generateMoonshineDecoderGraph, generateMoonshineEncoderGraph, generateNanoCodecDecoderGraph, generateQwen3_5VisionGraph, initGPU, loadKaniTTS, loadModel, loadMoonshine, moonshineEncoderFrames, mropeFreqDims, parseKaniConfig, parseMoonshineConfig, patchGemma4VisionClips, preprocessImage, preprocessImageGemma4, resolveDefaultRepo, resolveGemma4VisionInfo, smartResize };
|
package/dist/gpu/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as generateGemma4VisionGraph, D as resolveDefaultRepo, E as DEFAULT_MODELS, S as dequantizeMLXProjection, T as resolveGemma4VisionInfo, _ as smartResize, a as buildGemma4PosEmbeds, b as generateQwen3_5VisionGraph, c as buildMRoPECosSin, d as buildPositionIds, f as buildRotaryCosSin, g as preprocessImageGemma4, h as preprocessImage, i as buildGemma4PoolMatrix, l as buildMRoPEPositionIds, m as mropeFreqDims, n as GEMMA4_IMAGE_PROCESSOR, o as buildGemma4RotaryCosSin, p as buildVisionPositionTensors, r as QWEN3_5_IMAGE_PROCESSOR, s as buildGemma4VisionPositionTensors, t as WebGPUEngine, u as buildPosEmbeds, v as VisionExecutor, w as patchGemma4VisionClips, x as dequantizeGemma4VisionProjection, y as KaniTTS } from "../gpu-Ch2VuLdN.mjs";
|
|
2
2
|
import { C as parseKaniConfig, _ as generateNanoCodecDecoderGraph, a as generateMoonshineEncoderGraph, f as audioTokensToCodes, g as generateKaniTtsGraph, i as generateMoonshineDecoderGraph, o as moonshineEncoderFrames, r as MOONSHINE_REMAINING_WORK, s as parseMoonshineConfig } from "../architectures-C1I5V3Dt.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { a as loadMoonshine, h as initGPU, i as loadModel, n as MoonshineEncoderExecutor, o as Executor, r as loadKaniTTS, t as MoonshineSTT } from "../moonshine-stt-DNVw8v_Y.mjs";
|
|
3
|
+
import { a as loadMoonshine, h as initGPU, i as loadModel, n as MoonshineEncoderExecutor, o as Executor, r as loadKaniTTS, t as MoonshineSTT } from "../moonshine-stt-DR0-JbVW.mjs";
|
|
5
4
|
|
|
6
5
|
export { DEFAULT_MODELS, Executor, GEMMA4_IMAGE_PROCESSOR, KaniTTS, MOONSHINE_REMAINING_WORK, MoonshineEncoderExecutor, MoonshineSTT, QWEN3_5_IMAGE_PROCESSOR, VisionExecutor, WebGPUEngine, audioTokensToCodes, buildGemma4PoolMatrix, buildGemma4PosEmbeds, buildGemma4RotaryCosSin, buildGemma4VisionPositionTensors, buildMRoPECosSin, buildMRoPEPositionIds, buildPosEmbeds, buildPositionIds, buildRotaryCosSin, buildVisionPositionTensors, dequantizeGemma4VisionProjection, dequantizeMLXProjection, generateGemma4VisionGraph, generateKaniTtsGraph, generateMoonshineDecoderGraph, generateMoonshineEncoderGraph, generateNanoCodecDecoderGraph, generateQwen3_5VisionGraph, initGPU, loadKaniTTS, loadModel, loadMoonshine, moonshineEncoderFrames, mropeFreqDims, parseKaniConfig, parseMoonshineConfig, patchGemma4VisionClips, preprocessImage, preprocessImageGemma4, resolveDefaultRepo, resolveGemma4VisionInfo, smartResize };
|