@ismail-kattakath/mediapipe-react 0.1.2 → 0.2.0
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/README.md +72 -0
- package/dist/{chunk-73Z6K33X.mjs → chunk-F4K45QYO.mjs} +1 -1
- package/dist/{chunk-73Z6K33X.mjs.map → chunk-F4K45QYO.mjs.map} +1 -1
- package/dist/{chunk-OURWDMTD.js → chunk-LU5G37I6.js} +1 -1
- package/dist/chunk-LU5G37I6.js.map +1 -0
- package/dist/genai.js +2 -2
- package/dist/genai.mjs +1 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +1 -1
- package/dist/vision.d.mts +12 -2
- package/dist/vision.d.ts +12 -2
- package/dist/vision.js +150 -4
- package/dist/vision.js.map +1 -1
- package/dist/vision.mjs +150 -4
- package/dist/vision.mjs.map +1 -1
- package/dist/vision.worker.d.mts +2 -0
- package/dist/vision.worker.d.ts +2 -0
- package/dist/vision.worker.js +45 -0
- package/dist/vision.worker.js.map +1 -0
- package/dist/vision.worker.mjs +45 -0
- package/dist/vision.worker.mjs.map +1 -0
- package/package.json +8 -1
- package/dist/chunk-OURWDMTD.js.map +0 -1
package/README.md
CHANGED
|
@@ -243,6 +243,78 @@ function ChatInterface() {
|
|
|
243
243
|
}
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
+
### Vision
|
|
247
|
+
|
|
248
|
+
#### `useFaceLandmarker`
|
|
249
|
+
|
|
250
|
+
Hook for real-time face landmark detection.
|
|
251
|
+
|
|
252
|
+
**Parameters:**
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
interface UseFaceLandmarkerOptions {
|
|
256
|
+
modelPath?: string; // Path to face_landmarker.task
|
|
257
|
+
wasmPath?: string; // Path to wasm files
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Returns:**
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
{
|
|
265
|
+
detect: (input: HTMLVideoElement | HTMLCanvasElement | ImageData, timestamp: number) => void;
|
|
266
|
+
results: { faceLandmarks: { x: number; y: number; z: number }[][] } | null;
|
|
267
|
+
isLoading: boolean;
|
|
268
|
+
error: string | null;
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Example:**
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import {
|
|
276
|
+
useFaceLandmarker,
|
|
277
|
+
drawLandmarks,
|
|
278
|
+
} from "@ismail-kattakath/mediapipe-react/vision";
|
|
279
|
+
import { useEffect, useRef } from "react";
|
|
280
|
+
|
|
281
|
+
function FaceTracker() {
|
|
282
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
283
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
284
|
+
const { detect, results, isLoading } = useFaceLandmarker();
|
|
285
|
+
|
|
286
|
+
const processFrame = () => {
|
|
287
|
+
if (videoRef.current && canvasRef.current) {
|
|
288
|
+
const now = performance.now();
|
|
289
|
+
detect(videoRef.current, now);
|
|
290
|
+
|
|
291
|
+
const ctx = canvasRef.current.getContext("2d");
|
|
292
|
+
if (ctx) {
|
|
293
|
+
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
|
|
294
|
+
drawLandmarks(canvasRef.current, results);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
requestAnimationFrame(processFrame);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<>
|
|
303
|
+
<video ref={videoRef} autoPlay playsInline />
|
|
304
|
+
<canvas ref={canvasRef} />
|
|
305
|
+
</>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### `drawLandmarks`
|
|
311
|
+
|
|
312
|
+
Utility to draw face landmarks on a canvas.
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
drawLandmarks(canvas: HTMLCanvasElement, results: unknown): void
|
|
316
|
+
```
|
|
317
|
+
|
|
246
318
|
## Next.js / SSR Compatibility
|
|
247
319
|
|
|
248
320
|
This library is **fully compatible** with Next.js App Router and Server-Side Rendering.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/genai.ts","../src/index.tsx","../src/utils.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useMediaPipeContext } from \"./index\";\n\n/**\n * The Web Worker logic for MediaPipe GenAI.\n * This is stringified so it can be easily initialized as a Blob URL if the file-based worker fails.\n */\nconst workerScript = `\nimport { LlmInference, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai';\n\nlet llmInference = null;\n\nasync function checkGpuSupport() {\n if (!('gpu' in navigator)) {\n throw new Error('WebGPU is not supported in this browser.');\n }\n const gpu = navigator.gpu;\n const adapter = await gpu.requestAdapter();\n if (!adapter) {\n throw new Error('No appropriate GPU adapter found.');\n }\n}\n\nasync function initInference(modelPath, wasmPath) {\n try {\n await checkGpuSupport();\n const genai = await FilesetResolver.forGenAiTasks(wasmPath);\n llmInference = await LlmInference.createFromOptions(genai, {\n baseOptions: { modelAssetPath: modelPath },\n });\n self.postMessage({ type: 'INIT_COMPLETE' });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Unknown error during initialization' });\n }\n}\n\nself.onmessage = async (event) => {\n const { type, payload } = event.data;\n\n if (type === 'INIT') {\n const { modelPath, wasmPath } = payload;\n await initInference(modelPath, wasmPath);\n }\n\n if (type === 'GENERATE') {\n if (!llmInference) {\n self.postMessage({ type: 'ERROR', error: 'LLM Inference not initialized. Please ensure the model is loaded and WebGPU is supported.' });\n return;\n }\n\n try {\n const { prompt } = payload;\n llmInference.generateResponse(prompt, (partialText, done) => {\n self.postMessage({\n type: 'CHUNK',\n payload: { text: partialText, done }\n });\n });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Error generating response' });\n }\n }\n};\n`;\n\nexport interface UseLlmOptions {\n modelPath?: string;\n wasmPath?: string;\n}\n\nexport function useLlm(options: UseLlmOptions = {}) {\n const context = useMediaPipeContext();\n const [output, setOutput] = useState(\"\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [progress, setProgress] = useState(0);\n\n const workerRef = useRef<Worker | null>(null);\n\n // Use values from props if provided, otherwise fallback to context\n const modelPath = options.modelPath || context.modelPath;\n const wasmPath =\n options.wasmPath ||\n context.wasmPath ||\n \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai/wasm\";\n\n useEffect(() => {\n if (!context.isBrowser || !modelPath) return;\n\n // Early check for WebGPU support in the UI thread too\n if (!(\"gpu\" in navigator)) {\n setTimeout(() => setError(\"WebGPU is not supported in this browser.\"), 0);\n return;\n }\n\n let worker: Worker;\n\n try {\n // Attempt to load from the separate worker file (Vite/Next.js/Webpack friendly)\n worker = new Worker(new URL(\"./genai.worker\", import.meta.url), {\n type: \"module\",\n name: \"mediapipe-genai-worker\",\n });\n } catch (_e) {\n // Fallback to Blob-based worker if relative path fails\n console.warn(\"MediaPipe React: Falling back to Blob-based GenAI worker\");\n const blob = new Blob([workerScript], { type: \"application/javascript\" });\n worker = new Worker(URL.createObjectURL(blob));\n }\n\n workerRef.current = worker;\n\n worker.onmessage = (event) => {\n const { type, payload, error: workerError } = event.data;\n\n switch (type) {\n case \"INIT_COMPLETE\":\n setIsLoading(false);\n setProgress(100);\n break;\n case \"CHUNK\":\n setOutput((prev) => prev + payload.text);\n if (payload.done) {\n setIsLoading(false);\n }\n break;\n case \"ERROR\":\n setError(workerError || \"Worker encountered an error\");\n setIsLoading(false);\n break;\n }\n };\n\n setTimeout(() => {\n setIsLoading(true);\n setProgress(10); // Initial progress\n }, 0);\n\n worker.postMessage({\n type: \"INIT\",\n payload: { modelPath, wasmPath },\n });\n\n return () => {\n worker.terminate();\n };\n }, [context.isBrowser, modelPath, wasmPath]);\n\n const generate = useCallback((prompt: string) => {\n if (!workerRef.current) {\n setError(\"Worker not initialized\");\n return;\n }\n\n setOutput(\"\");\n setIsLoading(true);\n setError(null);\n\n workerRef.current.postMessage({\n type: \"GENERATE\",\n payload: { prompt },\n });\n }, []);\n\n return {\n output,\n isLoading,\n progress,\n error,\n generate,\n };\n}\n","\"use client\";\n\nimport React, { createContext, useContext, useMemo } from \"react\";\nimport { isBrowser } from \"./utils\";\n\nexport { useLlm } from \"./genai\";\nexport type { UseLlmOptions } from \"./genai\";\n\nexport interface MediaPipeContextType {\n wasmPath?: string;\n modelPath?: string;\n isBrowser: boolean;\n}\n\nconst MediaPipeContext = createContext<MediaPipeContextType | null>(null);\n\nexport interface MediaPipeProviderProps {\n children: React.ReactNode;\n wasmPath?: string;\n modelPath?: string;\n}\n\nexport const MediaPipeProvider: React.FC<MediaPipeProviderProps> = ({\n children,\n wasmPath,\n modelPath,\n}) => {\n const value = useMemo(\n () => ({\n wasmPath,\n modelPath,\n isBrowser,\n }),\n [wasmPath, modelPath],\n );\n\n return (\n <MediaPipeContext.Provider value={value}>\n {children}\n </MediaPipeContext.Provider>\n );\n};\n\nexport const useMediaPipeContext = () => {\n const context = useContext(MediaPipeContext);\n if (!context) {\n throw new Error(\n \"useMediaPipeContext must be used within a MediaPipeProvider\",\n );\n }\n return context;\n};\n","export const isBrowser = typeof window !== \"undefined\";\n"],"mappings":";AAEA,SAAS,WAAW,UAAU,aAAa,cAAc;;;
|
|
1
|
+
{"version":3,"sources":["../src/genai.ts","../src/index.tsx","../src/utils.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useMediaPipeContext } from \"./index\";\n\n/**\n * The Web Worker logic for MediaPipe GenAI.\n * This is stringified so it can be easily initialized as a Blob URL if the file-based worker fails.\n */\nconst workerScript = `\nimport { LlmInference, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai';\n\nlet llmInference = null;\n\nasync function checkGpuSupport() {\n if (!('gpu' in navigator)) {\n throw new Error('WebGPU is not supported in this browser.');\n }\n const gpu = navigator.gpu;\n const adapter = await gpu.requestAdapter();\n if (!adapter) {\n throw new Error('No appropriate GPU adapter found.');\n }\n}\n\nasync function initInference(modelPath, wasmPath) {\n try {\n await checkGpuSupport();\n const genai = await FilesetResolver.forGenAiTasks(wasmPath);\n llmInference = await LlmInference.createFromOptions(genai, {\n baseOptions: { modelAssetPath: modelPath },\n });\n self.postMessage({ type: 'INIT_COMPLETE' });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Unknown error during initialization' });\n }\n}\n\nself.onmessage = async (event) => {\n const { type, payload } = event.data;\n\n if (type === 'INIT') {\n const { modelPath, wasmPath } = payload;\n await initInference(modelPath, wasmPath);\n }\n\n if (type === 'GENERATE') {\n if (!llmInference) {\n self.postMessage({ type: 'ERROR', error: 'LLM Inference not initialized. Please ensure the model is loaded and WebGPU is supported.' });\n return;\n }\n\n try {\n const { prompt } = payload;\n llmInference.generateResponse(prompt, (partialText, done) => {\n self.postMessage({\n type: 'CHUNK',\n payload: { text: partialText, done }\n });\n });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Error generating response' });\n }\n }\n};\n`;\n\nexport interface UseLlmOptions {\n modelPath?: string;\n wasmPath?: string;\n}\n\nexport function useLlm(options: UseLlmOptions = {}) {\n const context = useMediaPipeContext();\n const [output, setOutput] = useState(\"\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [progress, setProgress] = useState(0);\n\n const workerRef = useRef<Worker | null>(null);\n\n // Use values from props if provided, otherwise fallback to context\n const modelPath = options.modelPath || context.modelPath;\n const wasmPath =\n options.wasmPath ||\n context.wasmPath ||\n \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai/wasm\";\n\n useEffect(() => {\n if (!context.isBrowser || !modelPath) return;\n\n // Early check for WebGPU support in the UI thread too\n if (!(\"gpu\" in navigator)) {\n setTimeout(() => setError(\"WebGPU is not supported in this browser.\"), 0);\n return;\n }\n\n let worker: Worker;\n\n try {\n // Attempt to load from the separate worker file (Vite/Next.js/Webpack friendly)\n worker = new Worker(new URL(\"./genai.worker\", import.meta.url), {\n type: \"module\",\n name: \"mediapipe-genai-worker\",\n });\n } catch (_e) {\n // Fallback to Blob-based worker if relative path fails\n console.warn(\"MediaPipe React: Falling back to Blob-based GenAI worker\");\n const blob = new Blob([workerScript], { type: \"application/javascript\" });\n worker = new Worker(URL.createObjectURL(blob));\n }\n\n workerRef.current = worker;\n\n worker.onmessage = (event) => {\n const { type, payload, error: workerError } = event.data;\n\n switch (type) {\n case \"INIT_COMPLETE\":\n setIsLoading(false);\n setProgress(100);\n break;\n case \"CHUNK\":\n setOutput((prev) => prev + payload.text);\n if (payload.done) {\n setIsLoading(false);\n }\n break;\n case \"ERROR\":\n setError(workerError || \"Worker encountered an error\");\n setIsLoading(false);\n break;\n }\n };\n\n setTimeout(() => {\n setIsLoading(true);\n setProgress(10); // Initial progress\n }, 0);\n\n worker.postMessage({\n type: \"INIT\",\n payload: { modelPath, wasmPath },\n });\n\n return () => {\n worker.terminate();\n };\n }, [context.isBrowser, modelPath, wasmPath]);\n\n const generate = useCallback((prompt: string) => {\n if (!workerRef.current) {\n setError(\"Worker not initialized\");\n return;\n }\n\n setOutput(\"\");\n setIsLoading(true);\n setError(null);\n\n workerRef.current.postMessage({\n type: \"GENERATE\",\n payload: { prompt },\n });\n }, []);\n\n return {\n output,\n isLoading,\n progress,\n error,\n generate,\n };\n}\n","\"use client\";\n\n// Dummy change to verify release workflow\n\nimport React, { createContext, useContext, useMemo } from \"react\";\nimport { isBrowser } from \"./utils\";\n\nexport { useLlm } from \"./genai\";\nexport type { UseLlmOptions } from \"./genai\";\n\nexport interface MediaPipeContextType {\n wasmPath?: string;\n modelPath?: string;\n isBrowser: boolean;\n}\n\nconst MediaPipeContext = createContext<MediaPipeContextType | null>(null);\n\nexport interface MediaPipeProviderProps {\n children: React.ReactNode;\n wasmPath?: string;\n modelPath?: string;\n}\n\nexport const MediaPipeProvider: React.FC<MediaPipeProviderProps> = ({\n children,\n wasmPath,\n modelPath,\n}) => {\n const value = useMemo(\n () => ({\n wasmPath,\n modelPath,\n isBrowser,\n }),\n [wasmPath, modelPath],\n );\n\n return (\n <MediaPipeContext.Provider value={value}>\n {children}\n </MediaPipeContext.Provider>\n );\n};\n\nexport const useMediaPipeContext = () => {\n const context = useContext(MediaPipeContext);\n if (!context) {\n throw new Error(\n \"useMediaPipeContext must be used within a MediaPipeProvider\",\n );\n }\n return context;\n};\n","export const isBrowser = typeof window !== \"undefined\";\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/chunk-LU5G37I6.js","../src/genai.ts","../src/index.tsx","../src/utils.ts"],"names":[],"mappings":"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","file":"/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/chunk-LU5G37I6.js","sourcesContent":[null,"\"use client\";\n\nimport { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useMediaPipeContext } from \"./index\";\n\n/**\n * The Web Worker logic for MediaPipe GenAI.\n * This is stringified so it can be easily initialized as a Blob URL if the file-based worker fails.\n */\nconst workerScript = `\nimport { LlmInference, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai';\n\nlet llmInference = null;\n\nasync function checkGpuSupport() {\n if (!('gpu' in navigator)) {\n throw new Error('WebGPU is not supported in this browser.');\n }\n const gpu = navigator.gpu;\n const adapter = await gpu.requestAdapter();\n if (!adapter) {\n throw new Error('No appropriate GPU adapter found.');\n }\n}\n\nasync function initInference(modelPath, wasmPath) {\n try {\n await checkGpuSupport();\n const genai = await FilesetResolver.forGenAiTasks(wasmPath);\n llmInference = await LlmInference.createFromOptions(genai, {\n baseOptions: { modelAssetPath: modelPath },\n });\n self.postMessage({ type: 'INIT_COMPLETE' });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Unknown error during initialization' });\n }\n}\n\nself.onmessage = async (event) => {\n const { type, payload } = event.data;\n\n if (type === 'INIT') {\n const { modelPath, wasmPath } = payload;\n await initInference(modelPath, wasmPath);\n }\n\n if (type === 'GENERATE') {\n if (!llmInference) {\n self.postMessage({ type: 'ERROR', error: 'LLM Inference not initialized. Please ensure the model is loaded and WebGPU is supported.' });\n return;\n }\n\n try {\n const { prompt } = payload;\n llmInference.generateResponse(prompt, (partialText, done) => {\n self.postMessage({\n type: 'CHUNK',\n payload: { text: partialText, done }\n });\n });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Error generating response' });\n }\n }\n};\n`;\n\nexport interface UseLlmOptions {\n modelPath?: string;\n wasmPath?: string;\n}\n\nexport function useLlm(options: UseLlmOptions = {}) {\n const context = useMediaPipeContext();\n const [output, setOutput] = useState(\"\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [progress, setProgress] = useState(0);\n\n const workerRef = useRef<Worker | null>(null);\n\n // Use values from props if provided, otherwise fallback to context\n const modelPath = options.modelPath || context.modelPath;\n const wasmPath =\n options.wasmPath ||\n context.wasmPath ||\n \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai/wasm\";\n\n useEffect(() => {\n if (!context.isBrowser || !modelPath) return;\n\n // Early check for WebGPU support in the UI thread too\n if (!(\"gpu\" in navigator)) {\n setTimeout(() => setError(\"WebGPU is not supported in this browser.\"), 0);\n return;\n }\n\n let worker: Worker;\n\n try {\n // Attempt to load from the separate worker file (Vite/Next.js/Webpack friendly)\n worker = new Worker(new URL(\"./genai.worker\", import.meta.url), {\n type: \"module\",\n name: \"mediapipe-genai-worker\",\n });\n } catch (_e) {\n // Fallback to Blob-based worker if relative path fails\n console.warn(\"MediaPipe React: Falling back to Blob-based GenAI worker\");\n const blob = new Blob([workerScript], { type: \"application/javascript\" });\n worker = new Worker(URL.createObjectURL(blob));\n }\n\n workerRef.current = worker;\n\n worker.onmessage = (event) => {\n const { type, payload, error: workerError } = event.data;\n\n switch (type) {\n case \"INIT_COMPLETE\":\n setIsLoading(false);\n setProgress(100);\n break;\n case \"CHUNK\":\n setOutput((prev) => prev + payload.text);\n if (payload.done) {\n setIsLoading(false);\n }\n break;\n case \"ERROR\":\n setError(workerError || \"Worker encountered an error\");\n setIsLoading(false);\n break;\n }\n };\n\n setTimeout(() => {\n setIsLoading(true);\n setProgress(10); // Initial progress\n }, 0);\n\n worker.postMessage({\n type: \"INIT\",\n payload: { modelPath, wasmPath },\n });\n\n return () => {\n worker.terminate();\n };\n }, [context.isBrowser, modelPath, wasmPath]);\n\n const generate = useCallback((prompt: string) => {\n if (!workerRef.current) {\n setError(\"Worker not initialized\");\n return;\n }\n\n setOutput(\"\");\n setIsLoading(true);\n setError(null);\n\n workerRef.current.postMessage({\n type: \"GENERATE\",\n payload: { prompt },\n });\n }, []);\n\n return {\n output,\n isLoading,\n progress,\n error,\n generate,\n };\n}\n","\"use client\";\n\n// Dummy change to verify release workflow\n\nimport React, { createContext, useContext, useMemo } from \"react\";\nimport { isBrowser } from \"./utils\";\n\nexport { useLlm } from \"./genai\";\nexport type { UseLlmOptions } from \"./genai\";\n\nexport interface MediaPipeContextType {\n wasmPath?: string;\n modelPath?: string;\n isBrowser: boolean;\n}\n\nconst MediaPipeContext = createContext<MediaPipeContextType | null>(null);\n\nexport interface MediaPipeProviderProps {\n children: React.ReactNode;\n wasmPath?: string;\n modelPath?: string;\n}\n\nexport const MediaPipeProvider: React.FC<MediaPipeProviderProps> = ({\n children,\n wasmPath,\n modelPath,\n}) => {\n const value = useMemo(\n () => ({\n wasmPath,\n modelPath,\n isBrowser,\n }),\n [wasmPath, modelPath],\n );\n\n return (\n <MediaPipeContext.Provider value={value}>\n {children}\n </MediaPipeContext.Provider>\n );\n};\n\nexport const useMediaPipeContext = () => {\n const context = useContext(MediaPipeContext);\n if (!context) {\n throw new Error(\n \"useMediaPipeContext must be used within a MediaPipeProvider\",\n );\n }\n return context;\n};\n","export const isBrowser = typeof window !== \"undefined\";\n"]}
|
package/dist/genai.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkLU5G37I6js = require('./chunk-LU5G37I6.js');
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
exports.useLlm =
|
|
7
|
+
exports.useLlm = _chunkLU5G37I6js.useLlm;
|
|
8
8
|
//# sourceMappingURL=genai.js.map
|
package/dist/genai.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkLU5G37I6js = require('./chunk-LU5G37I6.js');
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
exports.MediaPipeProvider =
|
|
11
|
+
exports.MediaPipeProvider = _chunkLU5G37I6js.MediaPipeProvider; exports.useLlm = _chunkLU5G37I6js.useLlm; exports.useMediaPipeContext = _chunkLU5G37I6js.useMediaPipeContext;
|
|
12
12
|
//# sourceMappingURL=index.js.map
|
package/dist/index.mjs
CHANGED
package/dist/vision.d.mts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
interface UseFaceLandmarkerOptions {
|
|
2
|
+
modelPath?: string;
|
|
3
|
+
wasmPath?: string;
|
|
4
|
+
}
|
|
5
|
+
declare function useFaceLandmarker(options?: UseFaceLandmarkerOptions): {
|
|
6
|
+
results: unknown;
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
error: string | null;
|
|
9
|
+
detect: (input: HTMLVideoElement | HTMLCanvasElement | ImageData, timestamp: number) => void;
|
|
10
|
+
};
|
|
11
|
+
declare function drawLandmarks(canvas: HTMLCanvasElement, results: unknown): void;
|
|
2
12
|
|
|
3
|
-
export {
|
|
13
|
+
export { type UseFaceLandmarkerOptions, drawLandmarks, useFaceLandmarker };
|
package/dist/vision.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
interface UseFaceLandmarkerOptions {
|
|
2
|
+
modelPath?: string;
|
|
3
|
+
wasmPath?: string;
|
|
4
|
+
}
|
|
5
|
+
declare function useFaceLandmarker(options?: UseFaceLandmarkerOptions): {
|
|
6
|
+
results: unknown;
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
error: string | null;
|
|
9
|
+
detect: (input: HTMLVideoElement | HTMLCanvasElement | ImageData, timestamp: number) => void;
|
|
10
|
+
};
|
|
11
|
+
declare function drawLandmarks(canvas: HTMLCanvasElement, results: unknown): void;
|
|
2
12
|
|
|
3
|
-
export {
|
|
13
|
+
export { type UseFaceLandmarkerOptions, drawLandmarks, useFaceLandmarker };
|
package/dist/vision.js
CHANGED
|
@@ -1,8 +1,154 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true})
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkLU5G37I6js = require('./chunk-LU5G37I6.js');
|
|
5
|
+
|
|
6
|
+
// src/vision.ts
|
|
7
|
+
var _react = require('react');
|
|
8
|
+
var workerScript = `
|
|
9
|
+
import { FaceLandmarker, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.mjs';
|
|
10
|
+
|
|
11
|
+
let faceLandmarker = null;
|
|
12
|
+
|
|
13
|
+
async function initFaceLandmarker(wasmPath, modelAssetPath) {
|
|
14
|
+
try {
|
|
15
|
+
const vision = await FilesetResolver.forVisionTasks(wasmPath);
|
|
16
|
+
faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
|
|
17
|
+
baseOptions: {
|
|
18
|
+
modelAssetPath,
|
|
19
|
+
delegate: 'GPU'
|
|
20
|
+
},
|
|
21
|
+
runningMode: 'VIDEO',
|
|
22
|
+
numFaces: 1
|
|
23
|
+
});
|
|
24
|
+
self.postMessage({ type: 'INIT_COMPLETE' });
|
|
25
|
+
} catch (error) {
|
|
26
|
+
self.postMessage({ type: 'ERROR', error: error.message || 'Failed to initialize Face Landmarker' });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
self.onmessage = async (event) => {
|
|
31
|
+
const { type, payload } = event.data;
|
|
32
|
+
|
|
33
|
+
if (type === 'INIT') {
|
|
34
|
+
const { wasmPath, modelAssetPath } = payload;
|
|
35
|
+
await initFaceLandmarker(wasmPath, modelAssetPath);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (type === 'DETECT') {
|
|
39
|
+
if (!faceLandmarker) {
|
|
40
|
+
self.postMessage({ type: 'ERROR', error: 'Face Landmarker not initialized' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const { image, timestamp } = payload;
|
|
46
|
+
const results = faceLandmarker.detectForVideo(image, timestamp);
|
|
47
|
+
self.postMessage({ type: 'RESULTS', payload: results });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
self.postMessage({ type: 'ERROR', error: error.message || 'Error during detection' });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
4
52
|
};
|
|
53
|
+
`;
|
|
54
|
+
function useFaceLandmarker(options = {}) {
|
|
55
|
+
const context = _chunkLU5G37I6js.useMediaPipeContext.call(void 0, );
|
|
56
|
+
const [results, setResults] = _react.useState.call(void 0, null);
|
|
57
|
+
const [isLoading, setIsLoading] = _react.useState.call(void 0, false);
|
|
58
|
+
const [error, setError] = _react.useState.call(void 0, null);
|
|
59
|
+
const workerRef = _react.useRef.call(void 0, null);
|
|
60
|
+
const modelPath = options.modelPath || context.modelPath || "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task";
|
|
61
|
+
const wasmPath = options.wasmPath || context.wasmPath || "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm";
|
|
62
|
+
_react.useEffect.call(void 0, () => {
|
|
63
|
+
if (!context.isBrowser) return;
|
|
64
|
+
let worker;
|
|
65
|
+
try {
|
|
66
|
+
worker = new Worker(new URL("./vision.worker", import.meta.url), {
|
|
67
|
+
type: "module",
|
|
68
|
+
name: "mediapipe-vision-worker"
|
|
69
|
+
});
|
|
70
|
+
} catch (_e) {
|
|
71
|
+
console.warn("MediaPipe React: Falling back to Blob-based Vision worker");
|
|
72
|
+
const blob = new Blob([workerScript], { type: "application/javascript" });
|
|
73
|
+
worker = new Worker(URL.createObjectURL(blob));
|
|
74
|
+
}
|
|
75
|
+
workerRef.current = worker;
|
|
76
|
+
worker.onmessage = (event) => {
|
|
77
|
+
const { type, payload, error: workerError } = event.data;
|
|
78
|
+
switch (type) {
|
|
79
|
+
case "INIT_COMPLETE":
|
|
80
|
+
setIsLoading(false);
|
|
81
|
+
break;
|
|
82
|
+
case "RESULTS":
|
|
83
|
+
setResults(payload);
|
|
84
|
+
break;
|
|
85
|
+
case "ERROR":
|
|
86
|
+
setError(workerError || "Worker encountered an error");
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
setIsLoading(true);
|
|
93
|
+
worker.postMessage({
|
|
94
|
+
type: "INIT",
|
|
95
|
+
payload: { wasmPath, modelAssetPath: modelPath }
|
|
96
|
+
});
|
|
97
|
+
}, 0);
|
|
98
|
+
return () => {
|
|
99
|
+
worker.terminate();
|
|
100
|
+
};
|
|
101
|
+
}, [context.isBrowser, modelPath, wasmPath]);
|
|
102
|
+
const detect = _react.useCallback.call(void 0,
|
|
103
|
+
(input, timestamp) => {
|
|
104
|
+
if (!workerRef.current || isLoading) return;
|
|
105
|
+
if (input instanceof HTMLVideoElement || input instanceof HTMLCanvasElement) {
|
|
106
|
+
createImageBitmap(input).then((imageBitmap) => {
|
|
107
|
+
_optionalChain([workerRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.postMessage, 'call', _3 => _3(
|
|
108
|
+
{
|
|
109
|
+
type: "DETECT",
|
|
110
|
+
payload: { image: imageBitmap, timestamp }
|
|
111
|
+
},
|
|
112
|
+
[imageBitmap]
|
|
113
|
+
)]);
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
workerRef.current.postMessage({
|
|
117
|
+
type: "DETECT",
|
|
118
|
+
payload: { image: input, timestamp }
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
[isLoading]
|
|
123
|
+
);
|
|
124
|
+
return {
|
|
125
|
+
results,
|
|
126
|
+
isLoading,
|
|
127
|
+
error,
|
|
128
|
+
detect
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function drawLandmarks(canvas, results) {
|
|
132
|
+
const ctx = canvas.getContext("2d");
|
|
133
|
+
if (!ctx || !results || typeof results !== "object") return;
|
|
134
|
+
const faceResults = results;
|
|
135
|
+
if (!faceResults.faceLandmarks) return;
|
|
136
|
+
ctx.save();
|
|
137
|
+
ctx.strokeStyle = "#00FF00";
|
|
138
|
+
ctx.lineWidth = 1;
|
|
139
|
+
for (const landmarks of faceResults.faceLandmarks) {
|
|
140
|
+
for (const landmark of landmarks) {
|
|
141
|
+
const x = landmark.x * canvas.width;
|
|
142
|
+
const y = landmark.y * canvas.height;
|
|
143
|
+
ctx.beginPath();
|
|
144
|
+
ctx.arc(x, y, 1, 0, 2 * Math.PI);
|
|
145
|
+
ctx.fill();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
ctx.restore();
|
|
149
|
+
}
|
|
150
|
+
|
|
5
151
|
|
|
6
152
|
|
|
7
|
-
exports.
|
|
153
|
+
exports.drawLandmarks = drawLandmarks; exports.useFaceLandmarker = useFaceLandmarker;
|
|
8
154
|
//# sourceMappingURL=vision.js.map
|
package/dist/vision.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/vision.js","../src/vision
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/vision.js","../src/vision.ts"],"names":[],"mappings":"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","file":"/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/vision.js","sourcesContent":[null,"\"use client\";\n\nimport { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useMediaPipeContext } from \"./index\";\n\n/**\n * The Web Worker logic for MediaPipe Vision (Face Landmarker).\n * Stringified for fallback initialization.\n */\nconst workerScript = `\nimport { FaceLandmarker, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.mjs';\n\nlet faceLandmarker = null;\n\nasync function initFaceLandmarker(wasmPath, modelAssetPath) {\n try {\n const vision = await FilesetResolver.forVisionTasks(wasmPath);\n faceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n baseOptions: {\n modelAssetPath,\n delegate: 'GPU'\n },\n runningMode: 'VIDEO',\n numFaces: 1\n });\n self.postMessage({ type: 'INIT_COMPLETE' });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Failed to initialize Face Landmarker' });\n }\n}\n\nself.onmessage = async (event) => {\n const { type, payload } = event.data;\n\n if (type === 'INIT') {\n const { wasmPath, modelAssetPath } = payload;\n await initFaceLandmarker(wasmPath, modelAssetPath);\n }\n\n if (type === 'DETECT') {\n if (!faceLandmarker) {\n self.postMessage({ type: 'ERROR', error: 'Face Landmarker not initialized' });\n return;\n }\n\n try {\n const { image, timestamp } = payload;\n const results = faceLandmarker.detectForVideo(image, timestamp);\n self.postMessage({ type: 'RESULTS', payload: results });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Error during detection' });\n }\n }\n};\n`;\n\nexport interface UseFaceLandmarkerOptions {\n modelPath?: string;\n wasmPath?: string;\n}\n\nexport function useFaceLandmarker(options: UseFaceLandmarkerOptions = {}) {\n const context = useMediaPipeContext();\n const [results, setResults] = useState<unknown>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const workerRef = useRef<Worker | null>(null);\n\n const modelPath =\n options.modelPath ||\n context.modelPath ||\n \"https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task\";\n const wasmPath =\n options.wasmPath ||\n context.wasmPath ||\n \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm\";\n\n useEffect(() => {\n if (!context.isBrowser) return;\n\n let worker: Worker;\n\n try {\n worker = new Worker(new URL(\"./vision.worker\", import.meta.url), {\n type: \"module\",\n name: \"mediapipe-vision-worker\",\n });\n } catch (_e) {\n console.warn(\"MediaPipe React: Falling back to Blob-based Vision worker\");\n const blob = new Blob([workerScript], { type: \"application/javascript\" });\n worker = new Worker(URL.createObjectURL(blob));\n }\n\n workerRef.current = worker;\n\n worker.onmessage = (event) => {\n const { type, payload, error: workerError } = event.data;\n\n switch (type) {\n case \"INIT_COMPLETE\":\n setIsLoading(false);\n break;\n case \"RESULTS\":\n setResults(payload);\n break;\n case \"ERROR\":\n setError(workerError || \"Worker encountered an error\");\n setIsLoading(false);\n break;\n }\n };\n\n setTimeout(() => {\n setIsLoading(true);\n worker.postMessage({\n type: \"INIT\",\n payload: { wasmPath, modelAssetPath: modelPath },\n });\n }, 0);\n\n return () => {\n worker.terminate();\n };\n }, [context.isBrowser, modelPath, wasmPath]);\n\n const detect = useCallback(\n (\n input: HTMLVideoElement | HTMLCanvasElement | ImageData,\n timestamp: number,\n ) => {\n if (!workerRef.current || isLoading) return;\n\n // For video elements, we need to create an ImageBitmap or similar if passing to worker\n // However, MediaPipe's detectForVideo in worker usually expects an ImageBitmap or similar offscreen canvas\n // For simplicity in this first version, we'll assume the worker can handle the input or we'll need to transfer it.\n // Actually, passing HTMLVideoElement to worker won't work. We need to create an ImageBitmap.\n\n if (\n input instanceof HTMLVideoElement ||\n input instanceof HTMLCanvasElement\n ) {\n createImageBitmap(input).then((imageBitmap) => {\n workerRef.current?.postMessage(\n {\n type: \"DETECT\",\n payload: { image: imageBitmap, timestamp },\n },\n [imageBitmap],\n );\n });\n } else {\n workerRef.current.postMessage({\n type: \"DETECT\",\n payload: { image: input, timestamp },\n });\n }\n },\n [isLoading],\n );\n\n return {\n results,\n isLoading,\n error,\n detect,\n };\n}\n\nexport function drawLandmarks(canvas: HTMLCanvasElement, results: unknown) {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx || !results || typeof results !== \"object\") return;\n\n const faceResults = results as {\n faceLandmarks?: { x: number; y: number; z: number }[][];\n };\n if (!faceResults.faceLandmarks) return;\n\n ctx.save();\n ctx.strokeStyle = \"#00FF00\";\n ctx.lineWidth = 1;\n\n for (const landmarks of faceResults.faceLandmarks) {\n for (const landmark of landmarks) {\n const x = landmark.x * canvas.width;\n const y = landmark.y * canvas.height;\n ctx.beginPath();\n ctx.arc(x, y, 1, 0, 2 * Math.PI);\n ctx.fill();\n }\n }\n ctx.restore();\n}\n"]}
|
package/dist/vision.mjs
CHANGED
|
@@ -1,8 +1,154 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
useMediaPipeContext
|
|
4
|
+
} from "./chunk-F4K45QYO.mjs";
|
|
5
|
+
|
|
6
|
+
// src/vision.ts
|
|
7
|
+
import { useEffect, useState, useCallback, useRef } from "react";
|
|
8
|
+
var workerScript = `
|
|
9
|
+
import { FaceLandmarker, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.mjs';
|
|
10
|
+
|
|
11
|
+
let faceLandmarker = null;
|
|
12
|
+
|
|
13
|
+
async function initFaceLandmarker(wasmPath, modelAssetPath) {
|
|
14
|
+
try {
|
|
15
|
+
const vision = await FilesetResolver.forVisionTasks(wasmPath);
|
|
16
|
+
faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
|
|
17
|
+
baseOptions: {
|
|
18
|
+
modelAssetPath,
|
|
19
|
+
delegate: 'GPU'
|
|
20
|
+
},
|
|
21
|
+
runningMode: 'VIDEO',
|
|
22
|
+
numFaces: 1
|
|
23
|
+
});
|
|
24
|
+
self.postMessage({ type: 'INIT_COMPLETE' });
|
|
25
|
+
} catch (error) {
|
|
26
|
+
self.postMessage({ type: 'ERROR', error: error.message || 'Failed to initialize Face Landmarker' });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
self.onmessage = async (event) => {
|
|
31
|
+
const { type, payload } = event.data;
|
|
32
|
+
|
|
33
|
+
if (type === 'INIT') {
|
|
34
|
+
const { wasmPath, modelAssetPath } = payload;
|
|
35
|
+
await initFaceLandmarker(wasmPath, modelAssetPath);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (type === 'DETECT') {
|
|
39
|
+
if (!faceLandmarker) {
|
|
40
|
+
self.postMessage({ type: 'ERROR', error: 'Face Landmarker not initialized' });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const { image, timestamp } = payload;
|
|
46
|
+
const results = faceLandmarker.detectForVideo(image, timestamp);
|
|
47
|
+
self.postMessage({ type: 'RESULTS', payload: results });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
self.postMessage({ type: 'ERROR', error: error.message || 'Error during detection' });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
4
52
|
};
|
|
53
|
+
`;
|
|
54
|
+
function useFaceLandmarker(options = {}) {
|
|
55
|
+
const context = useMediaPipeContext();
|
|
56
|
+
const [results, setResults] = useState(null);
|
|
57
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
58
|
+
const [error, setError] = useState(null);
|
|
59
|
+
const workerRef = useRef(null);
|
|
60
|
+
const modelPath = options.modelPath || context.modelPath || "https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task";
|
|
61
|
+
const wasmPath = options.wasmPath || context.wasmPath || "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm";
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!context.isBrowser) return;
|
|
64
|
+
let worker;
|
|
65
|
+
try {
|
|
66
|
+
worker = new Worker(new URL("./vision.worker", import.meta.url), {
|
|
67
|
+
type: "module",
|
|
68
|
+
name: "mediapipe-vision-worker"
|
|
69
|
+
});
|
|
70
|
+
} catch (_e) {
|
|
71
|
+
console.warn("MediaPipe React: Falling back to Blob-based Vision worker");
|
|
72
|
+
const blob = new Blob([workerScript], { type: "application/javascript" });
|
|
73
|
+
worker = new Worker(URL.createObjectURL(blob));
|
|
74
|
+
}
|
|
75
|
+
workerRef.current = worker;
|
|
76
|
+
worker.onmessage = (event) => {
|
|
77
|
+
const { type, payload, error: workerError } = event.data;
|
|
78
|
+
switch (type) {
|
|
79
|
+
case "INIT_COMPLETE":
|
|
80
|
+
setIsLoading(false);
|
|
81
|
+
break;
|
|
82
|
+
case "RESULTS":
|
|
83
|
+
setResults(payload);
|
|
84
|
+
break;
|
|
85
|
+
case "ERROR":
|
|
86
|
+
setError(workerError || "Worker encountered an error");
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
setIsLoading(true);
|
|
93
|
+
worker.postMessage({
|
|
94
|
+
type: "INIT",
|
|
95
|
+
payload: { wasmPath, modelAssetPath: modelPath }
|
|
96
|
+
});
|
|
97
|
+
}, 0);
|
|
98
|
+
return () => {
|
|
99
|
+
worker.terminate();
|
|
100
|
+
};
|
|
101
|
+
}, [context.isBrowser, modelPath, wasmPath]);
|
|
102
|
+
const detect = useCallback(
|
|
103
|
+
(input, timestamp) => {
|
|
104
|
+
if (!workerRef.current || isLoading) return;
|
|
105
|
+
if (input instanceof HTMLVideoElement || input instanceof HTMLCanvasElement) {
|
|
106
|
+
createImageBitmap(input).then((imageBitmap) => {
|
|
107
|
+
workerRef.current?.postMessage(
|
|
108
|
+
{
|
|
109
|
+
type: "DETECT",
|
|
110
|
+
payload: { image: imageBitmap, timestamp }
|
|
111
|
+
},
|
|
112
|
+
[imageBitmap]
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
workerRef.current.postMessage({
|
|
117
|
+
type: "DETECT",
|
|
118
|
+
payload: { image: input, timestamp }
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
[isLoading]
|
|
123
|
+
);
|
|
124
|
+
return {
|
|
125
|
+
results,
|
|
126
|
+
isLoading,
|
|
127
|
+
error,
|
|
128
|
+
detect
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function drawLandmarks(canvas, results) {
|
|
132
|
+
const ctx = canvas.getContext("2d");
|
|
133
|
+
if (!ctx || !results || typeof results !== "object") return;
|
|
134
|
+
const faceResults = results;
|
|
135
|
+
if (!faceResults.faceLandmarks) return;
|
|
136
|
+
ctx.save();
|
|
137
|
+
ctx.strokeStyle = "#00FF00";
|
|
138
|
+
ctx.lineWidth = 1;
|
|
139
|
+
for (const landmarks of faceResults.faceLandmarks) {
|
|
140
|
+
for (const landmark of landmarks) {
|
|
141
|
+
const x = landmark.x * canvas.width;
|
|
142
|
+
const y = landmark.y * canvas.height;
|
|
143
|
+
ctx.beginPath();
|
|
144
|
+
ctx.arc(x, y, 1, 0, 2 * Math.PI);
|
|
145
|
+
ctx.fill();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
ctx.restore();
|
|
149
|
+
}
|
|
5
150
|
export {
|
|
6
|
-
|
|
151
|
+
drawLandmarks,
|
|
152
|
+
useFaceLandmarker
|
|
7
153
|
};
|
|
8
154
|
//# sourceMappingURL=vision.mjs.map
|
package/dist/vision.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vision
|
|
1
|
+
{"version":3,"sources":["../src/vision.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useMediaPipeContext } from \"./index\";\n\n/**\n * The Web Worker logic for MediaPipe Vision (Face Landmarker).\n * Stringified for fallback initialization.\n */\nconst workerScript = `\nimport { FaceLandmarker, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/vision_bundle.mjs';\n\nlet faceLandmarker = null;\n\nasync function initFaceLandmarker(wasmPath, modelAssetPath) {\n try {\n const vision = await FilesetResolver.forVisionTasks(wasmPath);\n faceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n baseOptions: {\n modelAssetPath,\n delegate: 'GPU'\n },\n runningMode: 'VIDEO',\n numFaces: 1\n });\n self.postMessage({ type: 'INIT_COMPLETE' });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Failed to initialize Face Landmarker' });\n }\n}\n\nself.onmessage = async (event) => {\n const { type, payload } = event.data;\n\n if (type === 'INIT') {\n const { wasmPath, modelAssetPath } = payload;\n await initFaceLandmarker(wasmPath, modelAssetPath);\n }\n\n if (type === 'DETECT') {\n if (!faceLandmarker) {\n self.postMessage({ type: 'ERROR', error: 'Face Landmarker not initialized' });\n return;\n }\n\n try {\n const { image, timestamp } = payload;\n const results = faceLandmarker.detectForVideo(image, timestamp);\n self.postMessage({ type: 'RESULTS', payload: results });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Error during detection' });\n }\n }\n};\n`;\n\nexport interface UseFaceLandmarkerOptions {\n modelPath?: string;\n wasmPath?: string;\n}\n\nexport function useFaceLandmarker(options: UseFaceLandmarkerOptions = {}) {\n const context = useMediaPipeContext();\n const [results, setResults] = useState<unknown>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const workerRef = useRef<Worker | null>(null);\n\n const modelPath =\n options.modelPath ||\n context.modelPath ||\n \"https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task\";\n const wasmPath =\n options.wasmPath ||\n context.wasmPath ||\n \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision/wasm\";\n\n useEffect(() => {\n if (!context.isBrowser) return;\n\n let worker: Worker;\n\n try {\n worker = new Worker(new URL(\"./vision.worker\", import.meta.url), {\n type: \"module\",\n name: \"mediapipe-vision-worker\",\n });\n } catch (_e) {\n console.warn(\"MediaPipe React: Falling back to Blob-based Vision worker\");\n const blob = new Blob([workerScript], { type: \"application/javascript\" });\n worker = new Worker(URL.createObjectURL(blob));\n }\n\n workerRef.current = worker;\n\n worker.onmessage = (event) => {\n const { type, payload, error: workerError } = event.data;\n\n switch (type) {\n case \"INIT_COMPLETE\":\n setIsLoading(false);\n break;\n case \"RESULTS\":\n setResults(payload);\n break;\n case \"ERROR\":\n setError(workerError || \"Worker encountered an error\");\n setIsLoading(false);\n break;\n }\n };\n\n setTimeout(() => {\n setIsLoading(true);\n worker.postMessage({\n type: \"INIT\",\n payload: { wasmPath, modelAssetPath: modelPath },\n });\n }, 0);\n\n return () => {\n worker.terminate();\n };\n }, [context.isBrowser, modelPath, wasmPath]);\n\n const detect = useCallback(\n (\n input: HTMLVideoElement | HTMLCanvasElement | ImageData,\n timestamp: number,\n ) => {\n if (!workerRef.current || isLoading) return;\n\n // For video elements, we need to create an ImageBitmap or similar if passing to worker\n // However, MediaPipe's detectForVideo in worker usually expects an ImageBitmap or similar offscreen canvas\n // For simplicity in this first version, we'll assume the worker can handle the input or we'll need to transfer it.\n // Actually, passing HTMLVideoElement to worker won't work. We need to create an ImageBitmap.\n\n if (\n input instanceof HTMLVideoElement ||\n input instanceof HTMLCanvasElement\n ) {\n createImageBitmap(input).then((imageBitmap) => {\n workerRef.current?.postMessage(\n {\n type: \"DETECT\",\n payload: { image: imageBitmap, timestamp },\n },\n [imageBitmap],\n );\n });\n } else {\n workerRef.current.postMessage({\n type: \"DETECT\",\n payload: { image: input, timestamp },\n });\n }\n },\n [isLoading],\n );\n\n return {\n results,\n isLoading,\n error,\n detect,\n };\n}\n\nexport function drawLandmarks(canvas: HTMLCanvasElement, results: unknown) {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx || !results || typeof results !== \"object\") return;\n\n const faceResults = results as {\n faceLandmarks?: { x: number; y: number; z: number }[][];\n };\n if (!faceResults.faceLandmarks) return;\n\n ctx.save();\n ctx.strokeStyle = \"#00FF00\";\n ctx.lineWidth = 1;\n\n for (const landmarks of faceResults.faceLandmarks) {\n for (const landmark of landmarks) {\n const x = landmark.x * canvas.width;\n const y = landmark.y * canvas.height;\n ctx.beginPath();\n ctx.arc(x, y, 1, 0, 2 * Math.PI);\n ctx.fill();\n }\n }\n ctx.restore();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";// src/vision.worker.ts
|
|
2
|
+
var _tasksvision = require('@mediapipe/tasks-vision');
|
|
3
|
+
var faceLandmarker = null;
|
|
4
|
+
async function initFaceLandmarker(wasmPath, modelAssetPath) {
|
|
5
|
+
try {
|
|
6
|
+
const vision = await _tasksvision.FilesetResolver.forVisionTasks(wasmPath);
|
|
7
|
+
faceLandmarker = await _tasksvision.FaceLandmarker.createFromOptions(vision, {
|
|
8
|
+
baseOptions: {
|
|
9
|
+
modelAssetPath,
|
|
10
|
+
delegate: "GPU"
|
|
11
|
+
},
|
|
12
|
+
runningMode: "VIDEO",
|
|
13
|
+
numFaces: 1
|
|
14
|
+
});
|
|
15
|
+
self.postMessage({ type: "INIT_COMPLETE" });
|
|
16
|
+
} catch (error) {
|
|
17
|
+
const message = error instanceof Error ? error.message : "Failed to initialize Face Landmarker";
|
|
18
|
+
self.postMessage({ type: "ERROR", error: message });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
self.onmessage = async (event) => {
|
|
22
|
+
const { type, payload } = event.data;
|
|
23
|
+
if (type === "INIT") {
|
|
24
|
+
const { wasmPath, modelAssetPath } = payload;
|
|
25
|
+
await initFaceLandmarker(wasmPath, modelAssetPath);
|
|
26
|
+
}
|
|
27
|
+
if (type === "DETECT") {
|
|
28
|
+
if (!faceLandmarker) {
|
|
29
|
+
self.postMessage({
|
|
30
|
+
type: "ERROR",
|
|
31
|
+
error: "Face Landmarker not initialized"
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const { image, timestamp } = payload;
|
|
37
|
+
const results = faceLandmarker.detectForVideo(image, timestamp);
|
|
38
|
+
self.postMessage({ type: "RESULTS", payload: results });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : "Error during detection";
|
|
41
|
+
self.postMessage({ type: "ERROR", error: message });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=vision.worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/vision.worker.js","../src/vision.worker.ts"],"names":[],"mappings":"AAAA;ACAA,sDAAgD;AAEhD,IAAI,eAAA,EAAwC,IAAA;AAE5C,MAAA,SAAe,kBAAA,CAAmB,QAAA,EAAkB,cAAA,EAAwB;AAC1E,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,EAAS,MAAM,4BAAA,CAAgB,cAAA,CAAe,QAAQ,CAAA;AAC5D,IAAA,eAAA,EAAiB,MAAM,2BAAA,CAAe,iBAAA,CAAkB,MAAA,EAAQ;AAAA,MAC9D,WAAA,EAAa;AAAA,QACX,cAAA;AAAA,QACA,QAAA,EAAU;AAAA,MACZ,CAAA;AAAA,MACA,WAAA,EAAa,OAAA;AAAA,MACb,QAAA,EAAU;AAAA,IACZ,CAAC,CAAA;AACD,IAAA,IAAA,CAAK,WAAA,CAAY,EAAE,IAAA,EAAM,gBAAgB,CAAC,CAAA;AAAA,EAC5C,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,IAAA,MAAM,QAAA,EACJ,MAAA,WAAiB,MAAA,EACb,KAAA,CAAM,QAAA,EACN,sCAAA;AACN,IAAA,IAAA,CAAK,WAAA,CAAY,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,QAAQ,CAAC,CAAA;AAAA,EACpD;AACF;AAEA,IAAA,CAAK,UAAA,EAAY,MAAA,CAAO,KAAA,EAAA,GAAU;AAChC,EAAA,MAAM,EAAE,IAAA,EAAM,QAAQ,EAAA,EAAI,KAAA,CAAM,IAAA;AAEhC,EAAA,GAAA,CAAI,KAAA,IAAS,MAAA,EAAQ;AACnB,IAAA,MAAM,EAAE,QAAA,EAAU,eAAe,EAAA,EAAI,OAAA;AACrC,IAAA,MAAM,kBAAA,CAAmB,QAAA,EAAU,cAAc,CAAA;AAAA,EACnD;AAEA,EAAA,GAAA,CAAI,KAAA,IAAS,QAAA,EAAU;AACrB,IAAA,GAAA,CAAI,CAAC,cAAA,EAAgB;AACnB,MAAA,IAAA,CAAK,WAAA,CAAY;AAAA,QACf,IAAA,EAAM,OAAA;AAAA,QACN,KAAA,EAAO;AAAA,MACT,CAAC,CAAA;AACD,MAAA,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,KAAA,EAAO,UAAU,EAAA,EAAI,OAAA;AAC7B,MAAA,MAAM,QAAA,EAAU,cAAA,CAAe,cAAA,CAAe,KAAA,EAAO,SAAS,CAAA;AAC9D,MAAA,IAAA,CAAK,WAAA,CAAY,EAAE,IAAA,EAAM,SAAA,EAAW,OAAA,EAAS,QAAQ,CAAC,CAAA;AAAA,IACxD,EAAA,MAAA,CAAS,KAAA,EAAgB;AACvB,MAAA,MAAM,QAAA,EACJ,MAAA,WAAiB,MAAA,EAAQ,KAAA,CAAM,QAAA,EAAU,wBAAA;AAC3C,MAAA,IAAA,CAAK,WAAA,CAAY,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,QAAQ,CAAC,CAAA;AAAA,IACpD;AAAA,EACF;AACF,CAAA","file":"/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/vision.worker.js","sourcesContent":[null,"import { FaceLandmarker, FilesetResolver } from \"@mediapipe/tasks-vision\";\n\nlet faceLandmarker: FaceLandmarker | null = null;\n\nasync function initFaceLandmarker(wasmPath: string, modelAssetPath: string) {\n try {\n const vision = await FilesetResolver.forVisionTasks(wasmPath);\n faceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n baseOptions: {\n modelAssetPath,\n delegate: \"GPU\",\n },\n runningMode: \"VIDEO\",\n numFaces: 1,\n });\n self.postMessage({ type: \"INIT_COMPLETE\" });\n } catch (error: unknown) {\n const message =\n error instanceof Error\n ? error.message\n : \"Failed to initialize Face Landmarker\";\n self.postMessage({ type: \"ERROR\", error: message });\n }\n}\n\nself.onmessage = async (event) => {\n const { type, payload } = event.data;\n\n if (type === \"INIT\") {\n const { wasmPath, modelAssetPath } = payload;\n await initFaceLandmarker(wasmPath, modelAssetPath);\n }\n\n if (type === \"DETECT\") {\n if (!faceLandmarker) {\n self.postMessage({\n type: \"ERROR\",\n error: \"Face Landmarker not initialized\",\n });\n return;\n }\n\n try {\n const { image, timestamp } = payload;\n const results = faceLandmarker.detectForVideo(image, timestamp);\n self.postMessage({ type: \"RESULTS\", payload: results });\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : \"Error during detection\";\n self.postMessage({ type: \"ERROR\", error: message });\n }\n }\n};\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/vision.worker.ts
|
|
2
|
+
import { FaceLandmarker, FilesetResolver } from "@mediapipe/tasks-vision";
|
|
3
|
+
var faceLandmarker = null;
|
|
4
|
+
async function initFaceLandmarker(wasmPath, modelAssetPath) {
|
|
5
|
+
try {
|
|
6
|
+
const vision = await FilesetResolver.forVisionTasks(wasmPath);
|
|
7
|
+
faceLandmarker = await FaceLandmarker.createFromOptions(vision, {
|
|
8
|
+
baseOptions: {
|
|
9
|
+
modelAssetPath,
|
|
10
|
+
delegate: "GPU"
|
|
11
|
+
},
|
|
12
|
+
runningMode: "VIDEO",
|
|
13
|
+
numFaces: 1
|
|
14
|
+
});
|
|
15
|
+
self.postMessage({ type: "INIT_COMPLETE" });
|
|
16
|
+
} catch (error) {
|
|
17
|
+
const message = error instanceof Error ? error.message : "Failed to initialize Face Landmarker";
|
|
18
|
+
self.postMessage({ type: "ERROR", error: message });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
self.onmessage = async (event) => {
|
|
22
|
+
const { type, payload } = event.data;
|
|
23
|
+
if (type === "INIT") {
|
|
24
|
+
const { wasmPath, modelAssetPath } = payload;
|
|
25
|
+
await initFaceLandmarker(wasmPath, modelAssetPath);
|
|
26
|
+
}
|
|
27
|
+
if (type === "DETECT") {
|
|
28
|
+
if (!faceLandmarker) {
|
|
29
|
+
self.postMessage({
|
|
30
|
+
type: "ERROR",
|
|
31
|
+
error: "Face Landmarker not initialized"
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const { image, timestamp } = payload;
|
|
37
|
+
const results = faceLandmarker.detectForVideo(image, timestamp);
|
|
38
|
+
self.postMessage({ type: "RESULTS", payload: results });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : "Error during detection";
|
|
41
|
+
self.postMessage({ type: "ERROR", error: message });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=vision.worker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vision.worker.ts"],"sourcesContent":["import { FaceLandmarker, FilesetResolver } from \"@mediapipe/tasks-vision\";\n\nlet faceLandmarker: FaceLandmarker | null = null;\n\nasync function initFaceLandmarker(wasmPath: string, modelAssetPath: string) {\n try {\n const vision = await FilesetResolver.forVisionTasks(wasmPath);\n faceLandmarker = await FaceLandmarker.createFromOptions(vision, {\n baseOptions: {\n modelAssetPath,\n delegate: \"GPU\",\n },\n runningMode: \"VIDEO\",\n numFaces: 1,\n });\n self.postMessage({ type: \"INIT_COMPLETE\" });\n } catch (error: unknown) {\n const message =\n error instanceof Error\n ? error.message\n : \"Failed to initialize Face Landmarker\";\n self.postMessage({ type: \"ERROR\", error: message });\n }\n}\n\nself.onmessage = async (event) => {\n const { type, payload } = event.data;\n\n if (type === \"INIT\") {\n const { wasmPath, modelAssetPath } = payload;\n await initFaceLandmarker(wasmPath, modelAssetPath);\n }\n\n if (type === \"DETECT\") {\n if (!faceLandmarker) {\n self.postMessage({\n type: \"ERROR\",\n error: \"Face Landmarker not initialized\",\n });\n return;\n }\n\n try {\n const { image, timestamp } = payload;\n const results = faceLandmarker.detectForVideo(image, timestamp);\n self.postMessage({ type: \"RESULTS\", payload: results });\n } catch (error: unknown) {\n const message =\n error instanceof Error ? error.message : \"Error during detection\";\n self.postMessage({ type: \"ERROR\", error: message });\n }\n }\n};\n"],"mappings":";AAAA,SAAS,gBAAgB,uBAAuB;AAEhD,IAAI,iBAAwC;AAE5C,eAAe,mBAAmB,UAAkB,gBAAwB;AAC1E,MAAI;AACF,UAAM,SAAS,MAAM,gBAAgB,eAAe,QAAQ;AAC5D,qBAAiB,MAAM,eAAe,kBAAkB,QAAQ;AAAA,MAC9D,aAAa;AAAA,QACX;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,MACA,aAAa;AAAA,MACb,UAAU;AAAA,IACZ,CAAC;AACD,SAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAAA,EAC5C,SAAS,OAAgB;AACvB,UAAM,UACJ,iBAAiB,QACb,MAAM,UACN;AACN,SAAK,YAAY,EAAE,MAAM,SAAS,OAAO,QAAQ,CAAC;AAAA,EACpD;AACF;AAEA,KAAK,YAAY,OAAO,UAAU;AAChC,QAAM,EAAE,MAAM,QAAQ,IAAI,MAAM;AAEhC,MAAI,SAAS,QAAQ;AACnB,UAAM,EAAE,UAAU,eAAe,IAAI;AACrC,UAAM,mBAAmB,UAAU,cAAc;AAAA,EACnD;AAEA,MAAI,SAAS,UAAU;AACrB,QAAI,CAAC,gBAAgB;AACnB,WAAK,YAAY;AAAA,QACf,MAAM;AAAA,QACN,OAAO;AAAA,MACT,CAAC;AACD;AAAA,IACF;AAEA,QAAI;AACF,YAAM,EAAE,OAAO,UAAU,IAAI;AAC7B,YAAM,UAAU,eAAe,eAAe,OAAO,SAAS;AAC9D,WAAK,YAAY,EAAE,MAAM,WAAW,SAAS,QAAQ,CAAC;AAAA,IACxD,SAAS,OAAgB;AACvB,YAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU;AAC3C,WAAK,YAAY,EAAE,MAAM,SAAS,OAAO,QAAQ,CAAC;AAAA,IACpD;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ismail-kattakath/mediapipe-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,6 +30,11 @@
|
|
|
30
30
|
"import": "./dist/vision.mjs",
|
|
31
31
|
"require": "./dist/vision.js"
|
|
32
32
|
},
|
|
33
|
+
"./vision.worker": {
|
|
34
|
+
"types": "./dist/vision.worker.d.ts",
|
|
35
|
+
"import": "./dist/vision.worker.mjs",
|
|
36
|
+
"require": "./dist/vision.worker.js"
|
|
37
|
+
},
|
|
33
38
|
"./audio": {
|
|
34
39
|
"types": "./dist/audio.d.ts",
|
|
35
40
|
"import": "./dist/audio.mjs",
|
|
@@ -43,10 +48,12 @@
|
|
|
43
48
|
],
|
|
44
49
|
"peerDependencies": {
|
|
45
50
|
"@mediapipe/tasks-genai": "^0.10.0",
|
|
51
|
+
"@mediapipe/tasks-vision": "^0.10.0",
|
|
46
52
|
"react": ">=18",
|
|
47
53
|
"react-dom": ">=18"
|
|
48
54
|
},
|
|
49
55
|
"devDependencies": {
|
|
56
|
+
"@mediapipe/tasks-vision": "^0.10.32",
|
|
50
57
|
"@testing-library/dom": "^10.4.1",
|
|
51
58
|
"@testing-library/jest-dom": "^6.9.1",
|
|
52
59
|
"@testing-library/react": "^16.3.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/chunk-OURWDMTD.js","../src/genai.ts","../src/index.tsx","../src/utils.ts"],"names":[],"mappings":"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","file":"/home/runner/work/mediapipe-react/mediapipe-react/packages/core/dist/chunk-OURWDMTD.js","sourcesContent":[null,"\"use client\";\n\nimport { useEffect, useState, useCallback, useRef } from \"react\";\nimport { useMediaPipeContext } from \"./index\";\n\n/**\n * The Web Worker logic for MediaPipe GenAI.\n * This is stringified so it can be easily initialized as a Blob URL if the file-based worker fails.\n */\nconst workerScript = `\nimport { LlmInference, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai';\n\nlet llmInference = null;\n\nasync function checkGpuSupport() {\n if (!('gpu' in navigator)) {\n throw new Error('WebGPU is not supported in this browser.');\n }\n const gpu = navigator.gpu;\n const adapter = await gpu.requestAdapter();\n if (!adapter) {\n throw new Error('No appropriate GPU adapter found.');\n }\n}\n\nasync function initInference(modelPath, wasmPath) {\n try {\n await checkGpuSupport();\n const genai = await FilesetResolver.forGenAiTasks(wasmPath);\n llmInference = await LlmInference.createFromOptions(genai, {\n baseOptions: { modelAssetPath: modelPath },\n });\n self.postMessage({ type: 'INIT_COMPLETE' });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Unknown error during initialization' });\n }\n}\n\nself.onmessage = async (event) => {\n const { type, payload } = event.data;\n\n if (type === 'INIT') {\n const { modelPath, wasmPath } = payload;\n await initInference(modelPath, wasmPath);\n }\n\n if (type === 'GENERATE') {\n if (!llmInference) {\n self.postMessage({ type: 'ERROR', error: 'LLM Inference not initialized. Please ensure the model is loaded and WebGPU is supported.' });\n return;\n }\n\n try {\n const { prompt } = payload;\n llmInference.generateResponse(prompt, (partialText, done) => {\n self.postMessage({\n type: 'CHUNK',\n payload: { text: partialText, done }\n });\n });\n } catch (error) {\n self.postMessage({ type: 'ERROR', error: error.message || 'Error generating response' });\n }\n }\n};\n`;\n\nexport interface UseLlmOptions {\n modelPath?: string;\n wasmPath?: string;\n}\n\nexport function useLlm(options: UseLlmOptions = {}) {\n const context = useMediaPipeContext();\n const [output, setOutput] = useState(\"\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [progress, setProgress] = useState(0);\n\n const workerRef = useRef<Worker | null>(null);\n\n // Use values from props if provided, otherwise fallback to context\n const modelPath = options.modelPath || context.modelPath;\n const wasmPath =\n options.wasmPath ||\n context.wasmPath ||\n \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai/wasm\";\n\n useEffect(() => {\n if (!context.isBrowser || !modelPath) return;\n\n // Early check for WebGPU support in the UI thread too\n if (!(\"gpu\" in navigator)) {\n setTimeout(() => setError(\"WebGPU is not supported in this browser.\"), 0);\n return;\n }\n\n let worker: Worker;\n\n try {\n // Attempt to load from the separate worker file (Vite/Next.js/Webpack friendly)\n worker = new Worker(new URL(\"./genai.worker\", import.meta.url), {\n type: \"module\",\n name: \"mediapipe-genai-worker\",\n });\n } catch (_e) {\n // Fallback to Blob-based worker if relative path fails\n console.warn(\"MediaPipe React: Falling back to Blob-based GenAI worker\");\n const blob = new Blob([workerScript], { type: \"application/javascript\" });\n worker = new Worker(URL.createObjectURL(blob));\n }\n\n workerRef.current = worker;\n\n worker.onmessage = (event) => {\n const { type, payload, error: workerError } = event.data;\n\n switch (type) {\n case \"INIT_COMPLETE\":\n setIsLoading(false);\n setProgress(100);\n break;\n case \"CHUNK\":\n setOutput((prev) => prev + payload.text);\n if (payload.done) {\n setIsLoading(false);\n }\n break;\n case \"ERROR\":\n setError(workerError || \"Worker encountered an error\");\n setIsLoading(false);\n break;\n }\n };\n\n setTimeout(() => {\n setIsLoading(true);\n setProgress(10); // Initial progress\n }, 0);\n\n worker.postMessage({\n type: \"INIT\",\n payload: { modelPath, wasmPath },\n });\n\n return () => {\n worker.terminate();\n };\n }, [context.isBrowser, modelPath, wasmPath]);\n\n const generate = useCallback((prompt: string) => {\n if (!workerRef.current) {\n setError(\"Worker not initialized\");\n return;\n }\n\n setOutput(\"\");\n setIsLoading(true);\n setError(null);\n\n workerRef.current.postMessage({\n type: \"GENERATE\",\n payload: { prompt },\n });\n }, []);\n\n return {\n output,\n isLoading,\n progress,\n error,\n generate,\n };\n}\n","\"use client\";\n\nimport React, { createContext, useContext, useMemo } from \"react\";\nimport { isBrowser } from \"./utils\";\n\nexport { useLlm } from \"./genai\";\nexport type { UseLlmOptions } from \"./genai\";\n\nexport interface MediaPipeContextType {\n wasmPath?: string;\n modelPath?: string;\n isBrowser: boolean;\n}\n\nconst MediaPipeContext = createContext<MediaPipeContextType | null>(null);\n\nexport interface MediaPipeProviderProps {\n children: React.ReactNode;\n wasmPath?: string;\n modelPath?: string;\n}\n\nexport const MediaPipeProvider: React.FC<MediaPipeProviderProps> = ({\n children,\n wasmPath,\n modelPath,\n}) => {\n const value = useMemo(\n () => ({\n wasmPath,\n modelPath,\n isBrowser,\n }),\n [wasmPath, modelPath],\n );\n\n return (\n <MediaPipeContext.Provider value={value}>\n {children}\n </MediaPipeContext.Provider>\n );\n};\n\nexport const useMediaPipeContext = () => {\n const context = useContext(MediaPipeContext);\n if (!context) {\n throw new Error(\n \"useMediaPipeContext must be used within a MediaPipeProvider\",\n );\n }\n return context;\n};\n","export const isBrowser = typeof window !== \"undefined\";\n"]}
|