{"version":3,"file":"use-tabular-predictor.cjs","names":[],"sources":["../../src/tabular/use-tabular-predictor.ts"],"sourcesContent":["/**\n * React binding for {@link TabularPredictor}.\n *\n * Loading a model is async, cancellable, and has to be undone on unmount —\n * three things every component that touches inference gets wrong the same\n * way. The hook owns that lifecycle so a component only deals with\n * `status` and `predict`.\n */\n\nimport { useCallback, useEffect, useState } from \"react\";\nimport { useLatestRef } from \"@/hooks/use-latest-ref\";\n\nimport { fetchModelBytes, type ModelCacheOptions } from \"./cache\";\nimport { TabularPredictor } from \"./predictor\";\nimport type {\n    FeatureRow,\n    TabularModelSource,\n    TabularPrediction,\n    TabularPredictorOptions,\n} from \"./types\";\n\n/** Lifecycle of the model behind the hook. */\nexport type TabularPredictorStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Options for {@link useTabularPredictor}. */\nexport interface UseTabularPredictorOptions extends TabularPredictorOptions {\n    /**\n     * Cache the model bytes on the device, so later loads work offline.\n     *\n     * On by default when the source is a URL: an app that runs inference in\n     * the browser almost always wants it to keep working without a network,\n     * and the failure mode of not caching only shows up in a tunnel.\n     */\n    readonly cache?: boolean | ModelCacheOptions;\n}\n\n/** What {@link useTabularPredictor} returns. */\nexport interface UseTabularPredictorResult {\n    /** The loaded predictor, or `null` while loading or on error. */\n    readonly predictor: TabularPredictor | null;\n    /** Where the load is. */\n    readonly status: TabularPredictorStatus;\n    /** Why the load failed. */\n    readonly error: Error | null;\n    /** Whether the model is loaded and can answer. */\n    readonly isReady: boolean;\n    /**\n     * Predict for a batch of rows.\n     *\n     * @throws When called before the model is ready — awaiting `isReady`\n     *   is the caller's job, and a silent empty result would hide the bug.\n     */\n    readonly predict: (rows: readonly FeatureRow[]) => Promise<TabularPrediction>;\n    /** Load the model again, e.g. after a failure or a new version. */\n    readonly reload: () => void;\n}\n\n/**\n * Load a tabular model and keep it for the component's lifetime.\n *\n * @example\n * ```tsx\n * function RiskWidget() {\n *     const { predict, isReady } = useTabularPredictor(\"/models/risk-v3.onnx\");\n *     const [score, setScore] = useState<number | null>(null);\n *\n *     async function onSubmit(features: number[]) {\n *         const { probabilities } = await predict([features]);\n *         setScore(probabilities[0]?.[1] ?? null);\n *     }\n *\n *     return <button disabled={!isReady} onClick={() => onSubmit([1, 2, 3, 4])}>Score</button>;\n * }\n * ```\n *\n * @param source Model URL, or the bytes when the app already has them.\n *   Pass `null` to hold off loading (a gate, a lazy tab).\n * @param options Predictor options plus caching.\n * @returns The predictor, its status, and a `predict` bound to it.\n */\nexport function useTabularPredictor(\n    source: TabularModelSource | null,\n    options: UseTabularPredictorOptions = {},\n): UseTabularPredictorResult {\n    const [predictor, setPredictor] = useState<TabularPredictor | null>(null);\n    const [status, setStatus] = useState<TabularPredictorStatus>(\"idle\");\n    const [error, setError] = useState<Error | null>(null);\n    const [attempt, setAttempt] = useState(0);\n\n    const optionsRef = useLatestRef(options);\n\n    useEffect(() => {\n        if (source === null) {\n            setStatus(\"idle\");\n            return;\n        }\n\n        let cancelled = false;\n        let loaded: TabularPredictor | null = null;\n\n        setStatus(\"loading\");\n        setError(null);\n\n        void (async () => {\n            try {\n                const current = optionsRef.current;\n                const cache = current.cache ?? true;\n                const bytes =\n                    typeof source === \"string\" && cache !== false\n                        ? await fetchModelBytes(source, typeof cache === \"object\" ? cache : {})\n                        : source;\n                const created = await TabularPredictor.create(bytes, {\n                    providers: current.providers,\n                    warmup: current.warmup,\n                    sessionOptions: current.sessionOptions,\n                });\n                loaded = created;\n                if (cancelled) {\n                    await created.dispose();\n                    return;\n                }\n                setPredictor(created);\n                setStatus(\"ready\");\n            } catch (caught) {\n                if (cancelled) return;\n                setPredictor(null);\n                setError(caught instanceof Error ? caught : new Error(String(caught)));\n                setStatus(\"error\");\n            }\n        })();\n\n        return () => {\n            cancelled = true;\n            setPredictor(null);\n            void loaded?.dispose();\n        };\n    }, [source, attempt, optionsRef]);\n\n    const predict = useCallback(\n        async (rows: readonly FeatureRow[]): Promise<TabularPrediction> => {\n            if (predictor === null) {\n                throw new Error(\n                    \"predict() was called before the model finished loading. \" +\n                        \"Gate on `isReady`.\",\n                );\n            }\n            return await predictor.predict(rows);\n        },\n        [predictor],\n    );\n\n    const reload = useCallback(() => setAttempt((value) => value + 1), []);\n\n    return {\n        predictor,\n        status,\n        error,\n        isReady: status === \"ready\" && predictor !== null,\n        predict,\n        reload,\n    };\n}\n"],"mappings":"4HAgFA,SAAgB,EACZ,EACA,EAAsC,CAAC,EACd,CACzB,GAAM,CAAC,EAAW,IAAA,EAAgB,EAAA,SAAA,CAAkC,IAAI,EAClE,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAiC,MAAM,EAC7D,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAuB,IAAI,EAC/C,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAS,CAAC,EAElC,EAAa,EAAA,aAAa,CAAO,GAEvC,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,IAAW,KAAM,CACjB,EAAU,MAAM,EAChB,MACJ,CAEA,IAAI,EAAY,GACZ,EAAkC,KAiCtC,OA/BA,EAAU,SAAS,EACnB,EAAS,IAAI,GAEP,SAAY,CACd,GAAI,CACA,IAAM,EAAU,EAAW,QACrB,EAAQ,EAAQ,OAAS,GACzB,EACF,OAAO,GAAW,UAAY,IAAU,GAClC,MAAM,EAAA,gBAAgB,EAAQ,OAAO,GAAU,SAAW,EAAQ,CAAC,CAAC,EACpE,EACJ,EAAU,MAAM,EAAA,iBAAiB,OAAO,EAAO,CACjD,UAAW,EAAQ,UACnB,OAAQ,EAAQ,OAChB,eAAgB,EAAQ,cAC5B,CAAC,EAED,GADA,EAAS,EACL,EAAW,CACX,MAAM,EAAQ,QAAQ,EACtB,MACJ,CACA,EAAa,CAAO,EACpB,EAAU,OAAO,CACrB,OAAS,EAAQ,CACb,GAAI,EAAW,OACf,EAAa,IAAI,EACjB,EAAS,aAAkB,MAAQ,EAAa,MAAM,OAAO,CAAM,CAAC,CAAC,EACrE,EAAU,OAAO,CACrB,CACJ,EAAA,CAAG,MAEU,CACT,EAAY,GACZ,EAAa,IAAI,EACjB,GAAa,QAAQ,CACzB,CACJ,EAAG,CAAC,EAAQ,EAAS,CAAU,CAAC,EAEhC,IAAM,GAAA,EAAU,EAAA,YAAA,CACZ,KAAO,IAA4D,CAC/D,GAAI,IAAc,KACd,MAAU,MACN,4EAEJ,EAEJ,OAAO,MAAM,EAAU,QAAQ,CAAI,CACvC,EACA,CAAC,CAAS,CACd,EAEM,GAAA,EAAS,EAAA,YAAA,KAAkB,EAAY,GAAU,EAAQ,CAAC,EAAG,CAAC,CAAC,EAErE,MAAO,CACH,YACA,SACA,QACA,QAAS,IAAW,SAAW,IAAc,KAC7C,UACA,QACJ,CACJ"}