Julian Bilcke
commited on
Commit
Β·
6989c25
1
Parent(s):
c2ac436
fix image switch + add download button
Browse files- .gitignore +2 -0
- client/src/app.tsx +46 -23
- client/src/components/PoweredBy.tsx +1 -1
- public/index.js +175 -77
.gitignore
CHANGED
|
@@ -5,6 +5,8 @@ __pycache__/
|
|
| 5 |
**/*.py[cod]
|
| 6 |
*$py.class
|
| 7 |
|
|
|
|
|
|
|
| 8 |
# Model weights
|
| 9 |
**/*.pth
|
| 10 |
**/*.onnx
|
|
|
|
| 5 |
**/*.py[cod]
|
| 6 |
*$py.class
|
| 7 |
|
| 8 |
+
miniserver.py
|
| 9 |
+
|
| 10 |
# Model weights
|
| 11 |
**/*.pth
|
| 12 |
**/*.onnx
|
client/src/app.tsx
CHANGED
|
@@ -1,12 +1,11 @@
|
|
| 1 |
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
| 2 |
-
import {
|
| 3 |
|
| 4 |
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
| 5 |
import { truncateFileName } from './lib/utils';
|
| 6 |
import { useFaceLandmarkDetection } from './hooks/useFaceLandmarkDetection';
|
| 7 |
import { PoweredBy } from './components/PoweredBy';
|
| 8 |
import { Spinner } from './components/Spinner';
|
| 9 |
-
import { DoubleCard } from './components/DoubleCard';
|
| 10 |
import { useFacePokeAPI } from './hooks/useFacePokeAPI';
|
| 11 |
import { Layout } from './layout';
|
| 12 |
import { useMainStore } from './hooks/useMainStore';
|
|
@@ -22,6 +21,7 @@ export function App() {
|
|
| 22 |
const previewImage = useMainStore(s => s.previewImage);
|
| 23 |
const setPreviewImage = useMainStore(s => s.setPreviewImage);
|
| 24 |
const resetImage = useMainStore(s => s.resetImage);
|
|
|
|
| 25 |
|
| 26 |
const {
|
| 27 |
status,
|
|
@@ -65,12 +65,14 @@ export function App() {
|
|
| 65 |
const image = await convertImageToBase64(files[0]);
|
| 66 |
setPreviewImage(image);
|
| 67 |
setOriginalImage(image);
|
|
|
|
| 68 |
} catch (err) {
|
| 69 |
console.log(`failed to convert the image: `, err);
|
| 70 |
setImageFile(null);
|
| 71 |
setStatus('');
|
| 72 |
setPreviewImage('');
|
| 73 |
setOriginalImage('');
|
|
|
|
| 74 |
setFaceLandmarks([]);
|
| 75 |
setBlendShapes([]);
|
| 76 |
}
|
|
@@ -79,10 +81,22 @@ export function App() {
|
|
| 79 |
setStatus('');
|
| 80 |
setPreviewImage('');
|
| 81 |
setOriginalImage('');
|
|
|
|
| 82 |
setFaceLandmarks([]);
|
| 83 |
setBlendShapes([]);
|
| 84 |
}
|
| 85 |
-
}, [isMediaPipeReady, setImageFile, setPreviewImage, setOriginalImage, setFaceLandmarks, setBlendShapes, setStatus]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
const canDisplayBlendShapes = false
|
| 88 |
|
|
@@ -124,24 +138,35 @@ export function App() {
|
|
| 124 |
)}
|
| 125 |
<div className="mb-5 relative">
|
| 126 |
<div className="flex flex-row items-center justify-between w-full">
|
| 127 |
-
<div className="
|
| 128 |
-
<
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
</div>
|
| 146 |
{previewImage && <label className="mt-4 flex items-center">
|
| 147 |
<input
|
|
@@ -177,14 +202,12 @@ export function App() {
|
|
| 177 |
opacity: isDebugMode ? currentOpacity : 0.0,
|
| 178 |
transition: 'opacity 0.2s ease-in-out'
|
| 179 |
}}
|
| 180 |
-
|
| 181 |
/>
|
| 182 |
</div>
|
| 183 |
)}
|
| 184 |
{canDisplayBlendShapes && displayBlendShapes}
|
| 185 |
</div>
|
| 186 |
<PoweredBy />
|
| 187 |
-
|
| 188 |
</Layout>
|
| 189 |
);
|
| 190 |
}
|
|
|
|
| 1 |
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
| 2 |
+
import { Download } from 'lucide-react';
|
| 3 |
|
| 4 |
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
| 5 |
import { truncateFileName } from './lib/utils';
|
| 6 |
import { useFaceLandmarkDetection } from './hooks/useFaceLandmarkDetection';
|
| 7 |
import { PoweredBy } from './components/PoweredBy';
|
| 8 |
import { Spinner } from './components/Spinner';
|
|
|
|
| 9 |
import { useFacePokeAPI } from './hooks/useFacePokeAPI';
|
| 10 |
import { Layout } from './layout';
|
| 11 |
import { useMainStore } from './hooks/useMainStore';
|
|
|
|
| 21 |
const previewImage = useMainStore(s => s.previewImage);
|
| 22 |
const setPreviewImage = useMainStore(s => s.setPreviewImage);
|
| 23 |
const resetImage = useMainStore(s => s.resetImage);
|
| 24 |
+
const setOriginalImageHash = useMainStore(s => s.setOriginalImageHash);
|
| 25 |
|
| 26 |
const {
|
| 27 |
status,
|
|
|
|
| 65 |
const image = await convertImageToBase64(files[0]);
|
| 66 |
setPreviewImage(image);
|
| 67 |
setOriginalImage(image);
|
| 68 |
+
setOriginalImageHash('');
|
| 69 |
} catch (err) {
|
| 70 |
console.log(`failed to convert the image: `, err);
|
| 71 |
setImageFile(null);
|
| 72 |
setStatus('');
|
| 73 |
setPreviewImage('');
|
| 74 |
setOriginalImage('');
|
| 75 |
+
setOriginalImageHash('');
|
| 76 |
setFaceLandmarks([]);
|
| 77 |
setBlendShapes([]);
|
| 78 |
}
|
|
|
|
| 81 |
setStatus('');
|
| 82 |
setPreviewImage('');
|
| 83 |
setOriginalImage('');
|
| 84 |
+
setOriginalImageHash('');
|
| 85 |
setFaceLandmarks([]);
|
| 86 |
setBlendShapes([]);
|
| 87 |
}
|
| 88 |
+
}, [isMediaPipeReady, setImageFile, setPreviewImage, setOriginalImage, setOriginalImageHash, setFaceLandmarks, setBlendShapes, setStatus]);
|
| 89 |
+
|
| 90 |
+
const handleDownload = useCallback(() => {
|
| 91 |
+
if (previewImage) {
|
| 92 |
+
const link = document.createElement('a');
|
| 93 |
+
link.href = previewImage;
|
| 94 |
+
link.download = 'modified_image.png';
|
| 95 |
+
document.body.appendChild(link);
|
| 96 |
+
link.click();
|
| 97 |
+
document.body.removeChild(link);
|
| 98 |
+
}
|
| 99 |
+
}, [previewImage]);
|
| 100 |
|
| 101 |
const canDisplayBlendShapes = false
|
| 102 |
|
|
|
|
| 138 |
)}
|
| 139 |
<div className="mb-5 relative">
|
| 140 |
<div className="flex flex-row items-center justify-between w-full">
|
| 141 |
+
<div className="flex items-center space-x-2">
|
| 142 |
+
<div className="relative">
|
| 143 |
+
<input
|
| 144 |
+
id="imageInput"
|
| 145 |
+
type="file"
|
| 146 |
+
accept="image/*"
|
| 147 |
+
onChange={handleFileChange}
|
| 148 |
+
className="hidden"
|
| 149 |
+
disabled={!isMediaPipeReady}
|
| 150 |
+
/>
|
| 151 |
+
<label
|
| 152 |
+
htmlFor="imageInput"
|
| 153 |
+
className={`cursor-pointer inline-flex items-center px-3 h-10 border border-transparent text-sm font-medium rounded-md text-white ${
|
| 154 |
+
isMediaPipeReady ? 'bg-slate-600 hover:bg-slate-500' : 'bg-slate-500 cursor-not-allowed'
|
| 155 |
+
} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 shadow-xl`}
|
| 156 |
+
>
|
| 157 |
+
<Spinner />
|
| 158 |
+
{imageFile ? truncateFileName(imageFile.name, 32) : (isMediaPipeReady ? 'Choose a portrait photo (.jpg, .png, .webp)' : 'Initializing...')}
|
| 159 |
+
</label>
|
| 160 |
+
</div>
|
| 161 |
+
{previewImage && (
|
| 162 |
+
<button
|
| 163 |
+
onClick={handleDownload}
|
| 164 |
+
className="inline-flex items-center px-3 h-10 border border-transparent text-sm font-medium rounded-md text-white bg-zinc-600 hover:bg-zinc-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-zinc-500 shadow-xl"
|
| 165 |
+
>
|
| 166 |
+
<Download className="w-4 h-4 mr-2" />
|
| 167 |
+
Download
|
| 168 |
+
</button>
|
| 169 |
+
)}
|
| 170 |
</div>
|
| 171 |
{previewImage && <label className="mt-4 flex items-center">
|
| 172 |
<input
|
|
|
|
| 202 |
opacity: isDebugMode ? currentOpacity : 0.0,
|
| 203 |
transition: 'opacity 0.2s ease-in-out'
|
| 204 |
}}
|
|
|
|
| 205 |
/>
|
| 206 |
</div>
|
| 207 |
)}
|
| 208 |
{canDisplayBlendShapes && displayBlendShapes}
|
| 209 |
</div>
|
| 210 |
<PoweredBy />
|
|
|
|
| 211 |
</Layout>
|
| 212 |
);
|
| 213 |
}
|
client/src/components/PoweredBy.tsx
CHANGED
|
@@ -5,7 +5,7 @@ export function PoweredBy() {
|
|
| 5 |
style={{ textShadow: "rgb(255 255 255 / 80%) 0px 0px 2px" }}>
|
| 6 |
Best hosted on
|
| 7 |
</span>*/}
|
| 8 |
-
<span className="
|
| 9 |
<img src="/hf-logo.svg" alt="Hugging Face" className="w-5 h-5" />
|
| 10 |
</span>
|
| 11 |
<span className="text-neutral-900 text-sm font-semibold"
|
|
|
|
| 5 |
style={{ textShadow: "rgb(255 255 255 / 80%) 0px 0px 2px" }}>
|
| 6 |
Best hosted on
|
| 7 |
</span>*/}
|
| 8 |
+
<span className="mr-1">
|
| 9 |
<img src="/hf-logo.svg" alt="Hugging Face" className="w-5 h-5" />
|
| 10 |
</span>
|
| 11 |
<span className="text-neutral-900 text-sm font-semibold"
|
public/index.js
CHANGED
|
@@ -23683,8 +23683,77 @@ var require_lodash = __commonJS((exports, module) => {
|
|
| 23683 |
var client = __toESM(require_client(), 1);
|
| 23684 |
|
| 23685 |
// src/app.tsx
|
| 23686 |
-
var
|
|
|
|
|
|
|
|
|
|
| 23687 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23688 |
// src/components/ui/alert.tsx
|
| 23689 |
var React = __toESM(require_react(), 1);
|
| 23690 |
|
|
@@ -25133,7 +25202,7 @@ var AlertDescription = React.forwardRef(({ className, ...props }, ref) => jsx_de
|
|
| 25133 |
AlertDescription.displayName = "AlertDescription";
|
| 25134 |
|
| 25135 |
// src/hooks/useFaceLandmarkDetection.tsx
|
| 25136 |
-
var
|
| 25137 |
|
| 25138 |
// node_modules/@mediapipe/tasks-vision/vision_bundle.mjs
|
| 25139 |
var exports_vision_bundle = {};
|
|
@@ -29683,10 +29752,10 @@ var createStoreImpl = (createState) => {
|
|
| 29683 |
var createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
|
| 29684 |
|
| 29685 |
// node_modules/zustand/esm/react.mjs
|
| 29686 |
-
var
|
| 29687 |
var useStore = function(api, selector = identity) {
|
| 29688 |
-
const slice =
|
| 29689 |
-
|
| 29690 |
return slice;
|
| 29691 |
};
|
| 29692 |
var identity = (arg) => arg;
|
|
@@ -29932,21 +30001,21 @@ class FacePoke {
|
|
| 29932 |
var facePoke = new FacePoke;
|
| 29933 |
|
| 29934 |
// node_modules/beautiful-react-hooks/esm/useThrottledCallback.js
|
| 29935 |
-
var
|
| 29936 |
var import_lodash = __toESM(require_lodash(), 1);
|
| 29937 |
|
| 29938 |
// node_modules/beautiful-react-hooks/esm/useWillUnmount.js
|
| 29939 |
-
var
|
| 29940 |
|
| 29941 |
// node_modules/beautiful-react-hooks/esm/shared/isFunction.js
|
| 29942 |
var isFunction = (functionToCheck) => typeof functionToCheck === "function" && !!functionToCheck.constructor && !!functionToCheck.call && !!functionToCheck.apply;
|
| 29943 |
var isFunction_default = isFunction;
|
| 29944 |
|
| 29945 |
// node_modules/beautiful-react-hooks/esm/factory/createHandlerSetter.js
|
| 29946 |
-
var
|
| 29947 |
var createHandlerSetter = (callback) => {
|
| 29948 |
-
const handlerRef =
|
| 29949 |
-
const setHandler =
|
| 29950 |
if (typeof nextCallback !== "function") {
|
| 29951 |
throw new Error("the argument supplied to the \'setHandler\' function should be of type function");
|
| 29952 |
}
|
|
@@ -29958,9 +30027,9 @@ var createHandlerSetter_default = createHandlerSetter;
|
|
| 29958 |
|
| 29959 |
// node_modules/beautiful-react-hooks/esm/useWillUnmount.js
|
| 29960 |
var useWillUnmount = (callback) => {
|
| 29961 |
-
const mountRef =
|
| 29962 |
const [handler, setHandler] = createHandlerSetter_default(callback);
|
| 29963 |
-
|
| 29964 |
mountRef.current = true;
|
| 29965 |
return () => {
|
| 29966 |
if (isFunction_default(handler === null || handler === undefined ? undefined : handler.current) && mountRef.current) {
|
|
@@ -29978,15 +30047,15 @@ var defaultOptions = {
|
|
| 29978 |
trailing: true
|
| 29979 |
};
|
| 29980 |
var useThrottledCallback = (fn2, dependencies, wait = 600, options = defaultOptions) => {
|
| 29981 |
-
const throttled =
|
| 29982 |
-
|
| 29983 |
throttled.current = import_lodash.default(fn2, wait, options);
|
| 29984 |
}, [fn2, wait, options]);
|
| 29985 |
useWillUnmount_default(() => {
|
| 29986 |
var _a2;
|
| 29987 |
(_a2 = throttled.current) === null || _a2 === undefined || _a2.cancel();
|
| 29988 |
});
|
| 29989 |
-
return
|
| 29990 |
};
|
| 29991 |
var useThrottledCallback_default = useThrottledCallback;
|
| 29992 |
|
|
@@ -32761,34 +32830,34 @@ function useFaceLandmarkDetection() {
|
|
| 32761 |
const resetImage = useMainStore((s2) => s2.resetImage);
|
| 32762 |
window.debugJuju = useMainStore;
|
| 32763 |
const averageLatency = 220;
|
| 32764 |
-
const [faceLandmarks, setFaceLandmarks] =
|
| 32765 |
-
const [isMediaPipeReady, setIsMediaPipeReady] =
|
| 32766 |
-
const [isDrawingUtilsReady, setIsDrawingUtilsReady] =
|
| 32767 |
-
const [blendShapes, setBlendShapes] =
|
| 32768 |
-
const [dragStart, setDragStart] =
|
| 32769 |
-
const [dragEnd, setDragEnd] =
|
| 32770 |
-
const [isDragging, setIsDragging] =
|
| 32771 |
-
const [isWaitingForResponse, setIsWaitingForResponse] =
|
| 32772 |
-
const dragStartRef =
|
| 32773 |
-
const currentMousePosRef =
|
| 32774 |
-
const lastModifiedImageHashRef =
|
| 32775 |
-
const [currentLandmark, setCurrentLandmark] =
|
| 32776 |
-
const [previousLandmark, setPreviousLandmark] =
|
| 32777 |
-
const [currentOpacity, setCurrentOpacity] =
|
| 32778 |
-
const [previousOpacity, setPreviousOpacity] =
|
| 32779 |
-
const [isHovering, setIsHovering] =
|
| 32780 |
-
const canvasRef =
|
| 32781 |
-
const mediaPipeRef =
|
| 32782 |
faceLandmarker: null,
|
| 32783 |
drawingUtils: null
|
| 32784 |
});
|
| 32785 |
-
const setActiveLandmark =
|
| 32786 |
setPreviousLandmark(currentLandmark || null);
|
| 32787 |
setCurrentLandmark(newLandmark || null);
|
| 32788 |
setCurrentOpacity(0);
|
| 32789 |
setPreviousOpacity(1);
|
| 32790 |
}, [currentLandmark, setPreviousLandmark, setCurrentLandmark, setCurrentOpacity, setPreviousOpacity]);
|
| 32791 |
-
|
| 32792 |
console.log("Initializing MediaPipe...");
|
| 32793 |
let isMounted = true;
|
| 32794 |
const initializeMediaPipe = async () => {
|
|
@@ -32826,8 +32895,8 @@ function useFaceLandmarkDetection() {
|
|
| 32826 |
}
|
| 32827 |
};
|
| 32828 |
}, []);
|
| 32829 |
-
const [landmarkCenters, setLandmarkCenters] =
|
| 32830 |
-
const computeLandmarkCenters =
|
| 32831 |
const centers = {};
|
| 32832 |
const computeGroupCenter = (group) => {
|
| 32833 |
let sumX = 0, sumY = 0, sumZ = 0, count = 0;
|
|
@@ -32850,7 +32919,7 @@ function useFaceLandmarkDetection() {
|
|
| 32850 |
centers.background = { x: 0.5, y: 0.5, z: 0 };
|
| 32851 |
setLandmarkCenters(centers);
|
| 32852 |
}, []);
|
| 32853 |
-
const findClosestLandmark =
|
| 32854 |
const defaultLandmark = {
|
| 32855 |
group: "background",
|
| 32856 |
distance: 0,
|
|
@@ -32899,7 +32968,7 @@ function useFaceLandmarkDetection() {
|
|
| 32899 |
return defaultLandmark;
|
| 32900 |
}
|
| 32901 |
}, [landmarkCenters]);
|
| 32902 |
-
const detectFaceLandmarks =
|
| 32903 |
if (!isMediaPipeReady) {
|
| 32904 |
console.log("MediaPipe not ready. Skipping detection.");
|
| 32905 |
return;
|
|
@@ -32925,7 +32994,7 @@ function useFaceLandmarkDetection() {
|
|
| 32925 |
drawLandmarks(faceLandmarkerResult.faceLandmarks[0], canvasRef.current, drawingUtils);
|
| 32926 |
}
|
| 32927 |
}, [isMediaPipeReady, isDrawingUtilsReady, computeLandmarkCenters]);
|
| 32928 |
-
const drawLandmarks =
|
| 32929 |
const ctx = canvas.getContext("2d");
|
| 32930 |
if (!ctx)
|
| 32931 |
return;
|
|
@@ -32951,12 +33020,12 @@ function useFaceLandmarkDetection() {
|
|
| 32951 |
img.src = previewImage;
|
| 32952 |
}
|
| 32953 |
}, [previewImage, currentLandmark, previousLandmark, currentOpacity, previousOpacity]);
|
| 32954 |
-
|
| 32955 |
if (isMediaPipeReady && isDrawingUtilsReady && faceLandmarks.length > 0 && canvasRef.current && mediaPipeRef.current.drawingUtils) {
|
| 32956 |
drawLandmarks(faceLandmarks[0], canvasRef.current, mediaPipeRef.current.drawingUtils);
|
| 32957 |
}
|
| 32958 |
}, [isMediaPipeReady, isDrawingUtilsReady, faceLandmarks, currentLandmark, previousLandmark, currentOpacity, previousOpacity, drawLandmarks]);
|
| 32959 |
-
|
| 32960 |
let animationFrame;
|
| 32961 |
const animate = () => {
|
| 32962 |
setCurrentOpacity((prev) => Math.min(prev + 0.2, 1));
|
|
@@ -32968,7 +33037,7 @@ function useFaceLandmarkDetection() {
|
|
| 32968 |
animationFrame = requestAnimationFrame(animate);
|
| 32969 |
return () => cancelAnimationFrame(animationFrame);
|
| 32970 |
}, [currentLandmark]);
|
| 32971 |
-
const canvasRefCallback =
|
| 32972 |
if (node !== null) {
|
| 32973 |
const ctx = node.getContext("2d");
|
| 32974 |
if (ctx) {
|
|
@@ -32984,7 +33053,7 @@ function useFaceLandmarkDetection() {
|
|
| 32984 |
canvasRef.current = node;
|
| 32985 |
}
|
| 32986 |
}, []);
|
| 32987 |
-
|
| 32988 |
if (!isMediaPipeReady) {
|
| 32989 |
console.log("MediaPipe not ready. Skipping landmark detection.");
|
| 32990 |
return;
|
|
@@ -32999,7 +33068,7 @@ function useFaceLandmarkDetection() {
|
|
| 32999 |
}
|
| 33000 |
detectFaceLandmarks(previewImage);
|
| 33001 |
}, [isMediaPipeReady, isDrawingUtilsReady, previewImage]);
|
| 33002 |
-
const modifyImage =
|
| 33003 |
const {
|
| 33004 |
originalImage: originalImage2,
|
| 33005 |
originalImageHash: originalImageHash2,
|
|
@@ -33090,13 +33159,13 @@ function useFaceLandmarkDetection() {
|
|
| 33090 |
const modifyImageWithRateLimit = useThrottledCallback_default((params) => {
|
| 33091 |
modifyImage(params);
|
| 33092 |
}, [modifyImage], averageLatency);
|
| 33093 |
-
const handleMouseEnter =
|
| 33094 |
setIsHovering(true);
|
| 33095 |
}, []);
|
| 33096 |
-
const handleMouseLeave =
|
| 33097 |
setIsHovering(false);
|
| 33098 |
}, []);
|
| 33099 |
-
const handleMouseDown =
|
| 33100 |
if (!canvasRef.current)
|
| 33101 |
return;
|
| 33102 |
const rect = canvasRef.current.getBoundingClientRect();
|
|
@@ -33108,7 +33177,7 @@ function useFaceLandmarkDetection() {
|
|
| 33108 |
setDragStart({ x: x2, y: y2 });
|
| 33109 |
dragStartRef.current = { x: x2, y: y2 };
|
| 33110 |
}, [findClosestLandmark, setActiveLandmark, setDragStart]);
|
| 33111 |
-
const handleMouseMove =
|
| 33112 |
if (!canvasRef.current)
|
| 33113 |
return;
|
| 33114 |
const rect = canvasRef.current.getBoundingClientRect();
|
|
@@ -33134,7 +33203,7 @@ function useFaceLandmarkDetection() {
|
|
| 33134 |
setIsHovering(true);
|
| 33135 |
}
|
| 33136 |
}, [currentLandmark, dragStart, setIsHovering, setActiveLandmark, setIsDragging, modifyImageWithRateLimit, landmarkCenters]);
|
| 33137 |
-
const handleMouseUp =
|
| 33138 |
if (!canvasRef.current)
|
| 33139 |
return;
|
| 33140 |
const rect = canvasRef.current.getBoundingClientRect();
|
|
@@ -33156,7 +33225,7 @@ function useFaceLandmarkDetection() {
|
|
| 33156 |
dragStartRef.current = null;
|
| 33157 |
setActiveLandmark(undefined);
|
| 33158 |
}, [currentLandmark, isDragging, modifyImageWithRateLimit, findClosestLandmark, setActiveLandmark, landmarkCenters, modifyImageWithRateLimit, setIsDragging]);
|
| 33159 |
-
|
| 33160 |
facePoke.setOnModifiedImage((image, image_hash) => {
|
| 33161 |
if (image) {
|
| 33162 |
setPreviewImage(image);
|
|
@@ -33192,7 +33261,7 @@ function PoweredBy() {
|
|
| 33192 |
className: "flex flex-row items-center justify-center font-sans mt-4 w-full",
|
| 33193 |
children: [
|
| 33194 |
jsx_dev_runtime2.jsxDEV("span", {
|
| 33195 |
-
className: "
|
| 33196 |
children: jsx_dev_runtime2.jsxDEV("img", {
|
| 33197 |
src: "/hf-logo.svg",
|
| 33198 |
alt: "Hugging Face",
|
|
@@ -33226,17 +33295,17 @@ function Spinner() {
|
|
| 33226 |
}
|
| 33227 |
|
| 33228 |
// src/hooks/useFacePokeAPI.ts
|
| 33229 |
-
var
|
| 33230 |
function useFacePokeAPI() {
|
| 33231 |
-
const [status, setStatus] =
|
| 33232 |
-
const [isDebugMode, setIsDebugMode] =
|
| 33233 |
-
const [interruptMessage, setInterruptMessage] =
|
| 33234 |
-
const [isLoading, setIsLoading] =
|
| 33235 |
-
|
| 33236 |
const urlParams = new URLSearchParams(window.location.search);
|
| 33237 |
setIsDebugMode(urlParams.get("debug") === "true");
|
| 33238 |
}, []);
|
| 33239 |
-
|
| 33240 |
const handleInterruption = (event) => {
|
| 33241 |
setInterruptMessage(event.detail.message);
|
| 33242 |
};
|
|
@@ -33303,6 +33372,7 @@ function App() {
|
|
| 33303 |
const previewImage = useMainStore((s2) => s2.previewImage);
|
| 33304 |
const setPreviewImage = useMainStore((s2) => s2.setPreviewImage);
|
| 33305 |
const resetImage = useMainStore((s2) => s2.resetImage);
|
|
|
|
| 33306 |
const {
|
| 33307 |
status,
|
| 33308 |
setStatus,
|
|
@@ -33326,8 +33396,8 @@ function App() {
|
|
| 33326 |
handleMouseLeave,
|
| 33327 |
currentOpacity
|
| 33328 |
} = useFaceLandmarkDetection();
|
| 33329 |
-
const videoRef =
|
| 33330 |
-
const handleFileChange =
|
| 33331 |
const files = event.target.files;
|
| 33332 |
if (files && files[0]) {
|
| 33333 |
setImageFile(files[0]);
|
|
@@ -33336,12 +33406,14 @@ function App() {
|
|
| 33336 |
const image = await convertImageToBase64(files[0]);
|
| 33337 |
setPreviewImage(image);
|
| 33338 |
setOriginalImage(image);
|
|
|
|
| 33339 |
} catch (err) {
|
| 33340 |
console.log(`failed to convert the image: `, err);
|
| 33341 |
setImageFile(null);
|
| 33342 |
setStatus("");
|
| 33343 |
setPreviewImage("");
|
| 33344 |
setOriginalImage("");
|
|
|
|
| 33345 |
setFaceLandmarks([]);
|
| 33346 |
setBlendShapes([]);
|
| 33347 |
}
|
|
@@ -33350,12 +33422,23 @@ function App() {
|
|
| 33350 |
setStatus("");
|
| 33351 |
setPreviewImage("");
|
| 33352 |
setOriginalImage("");
|
|
|
|
| 33353 |
setFaceLandmarks([]);
|
| 33354 |
setBlendShapes([]);
|
| 33355 |
}
|
| 33356 |
-
}, [isMediaPipeReady, setImageFile, setPreviewImage, setOriginalImage, setFaceLandmarks, setBlendShapes, setStatus]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33357 |
const canDisplayBlendShapes = false;
|
| 33358 |
-
const displayBlendShapes =
|
| 33359 |
className: "mt-4",
|
| 33360 |
children: [
|
| 33361 |
jsx_dev_runtime5.jsxDEV("h3", {
|
|
@@ -33417,22 +33500,37 @@ function App() {
|
|
| 33417 |
className: "flex flex-row items-center justify-between w-full",
|
| 33418 |
children: [
|
| 33419 |
jsx_dev_runtime5.jsxDEV("div", {
|
| 33420 |
-
className: "
|
| 33421 |
children: [
|
| 33422 |
-
jsx_dev_runtime5.jsxDEV("
|
| 33423 |
-
|
| 33424 |
-
|
| 33425 |
-
|
| 33426 |
-
|
| 33427 |
-
|
| 33428 |
-
|
| 33429 |
-
|
| 33430 |
-
|
| 33431 |
-
|
| 33432 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33433 |
children: [
|
| 33434 |
-
jsx_dev_runtime5.jsxDEV(
|
| 33435 |
-
|
|
|
|
|
|
|
| 33436 |
]
|
| 33437 |
}, undefined, true, undefined, this)
|
| 33438 |
]
|
|
|
|
| 23683 |
var client = __toESM(require_client(), 1);
|
| 23684 |
|
| 23685 |
// src/app.tsx
|
| 23686 |
+
var import_react9 = __toESM(require_react(), 1);
|
| 23687 |
+
|
| 23688 |
+
// node_modules/lucide-react/dist/esm/createLucideIcon.js
|
| 23689 |
+
var import_react2 = __toESM(require_react(), 1);
|
| 23690 |
|
| 23691 |
+
// node_modules/lucide-react/dist/esm/shared/src/utils.js
|
| 23692 |
+
var toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
| 23693 |
+
var mergeClasses = (...classes) => classes.filter((className, index, array) => {
|
| 23694 |
+
return Boolean(className) && array.indexOf(className) === index;
|
| 23695 |
+
}).join(" ");
|
| 23696 |
+
|
| 23697 |
+
// node_modules/lucide-react/dist/esm/Icon.js
|
| 23698 |
+
var import_react = __toESM(require_react(), 1);
|
| 23699 |
+
|
| 23700 |
+
// node_modules/lucide-react/dist/esm/defaultAttributes.js
|
| 23701 |
+
var defaultAttributes = {
|
| 23702 |
+
xmlns: "http://www.w3.org/2000/svg",
|
| 23703 |
+
width: 24,
|
| 23704 |
+
height: 24,
|
| 23705 |
+
viewBox: "0 0 24 24",
|
| 23706 |
+
fill: "none",
|
| 23707 |
+
stroke: "currentColor",
|
| 23708 |
+
strokeWidth: 2,
|
| 23709 |
+
strokeLinecap: "round",
|
| 23710 |
+
strokeLinejoin: "round"
|
| 23711 |
+
};
|
| 23712 |
+
|
| 23713 |
+
// node_modules/lucide-react/dist/esm/Icon.js
|
| 23714 |
+
var Icon = import_react.forwardRef(({
|
| 23715 |
+
color = "currentColor",
|
| 23716 |
+
size = 24,
|
| 23717 |
+
strokeWidth = 2,
|
| 23718 |
+
absoluteStrokeWidth,
|
| 23719 |
+
className = "",
|
| 23720 |
+
children,
|
| 23721 |
+
iconNode,
|
| 23722 |
+
...rest
|
| 23723 |
+
}, ref) => {
|
| 23724 |
+
return import_react.createElement("svg", {
|
| 23725 |
+
ref,
|
| 23726 |
+
...defaultAttributes,
|
| 23727 |
+
width: size,
|
| 23728 |
+
height: size,
|
| 23729 |
+
stroke: color,
|
| 23730 |
+
strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
|
| 23731 |
+
className: mergeClasses("lucide", className),
|
| 23732 |
+
...rest
|
| 23733 |
+
}, [
|
| 23734 |
+
...iconNode.map(([tag, attrs]) => import_react.createElement(tag, attrs)),
|
| 23735 |
+
...Array.isArray(children) ? children : [children]
|
| 23736 |
+
]);
|
| 23737 |
+
});
|
| 23738 |
+
|
| 23739 |
+
// node_modules/lucide-react/dist/esm/createLucideIcon.js
|
| 23740 |
+
var createLucideIcon = (iconName, iconNode) => {
|
| 23741 |
+
const Component = import_react2.forwardRef(({ className, ...props }, ref) => import_react2.createElement(Icon, {
|
| 23742 |
+
ref,
|
| 23743 |
+
iconNode,
|
| 23744 |
+
className: mergeClasses(`lucide-${toKebabCase(iconName)}`, className),
|
| 23745 |
+
...props
|
| 23746 |
+
}));
|
| 23747 |
+
Component.displayName = `${iconName}`;
|
| 23748 |
+
return Component;
|
| 23749 |
+
};
|
| 23750 |
+
|
| 23751 |
+
// node_modules/lucide-react/dist/esm/icons/download.js
|
| 23752 |
+
var Download = createLucideIcon("Download", [
|
| 23753 |
+
["path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4", key: "ih7n3h" }],
|
| 23754 |
+
["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }],
|
| 23755 |
+
["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
|
| 23756 |
+
]);
|
| 23757 |
// src/components/ui/alert.tsx
|
| 23758 |
var React = __toESM(require_react(), 1);
|
| 23759 |
|
|
|
|
| 25202 |
AlertDescription.displayName = "AlertDescription";
|
| 25203 |
|
| 25204 |
// src/hooks/useFaceLandmarkDetection.tsx
|
| 25205 |
+
var import_react7 = __toESM(require_react(), 1);
|
| 25206 |
|
| 25207 |
// node_modules/@mediapipe/tasks-vision/vision_bundle.mjs
|
| 25208 |
var exports_vision_bundle = {};
|
|
|
|
| 29752 |
var createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
|
| 29753 |
|
| 29754 |
// node_modules/zustand/esm/react.mjs
|
| 29755 |
+
var import_react3 = __toESM(require_react(), 1);
|
| 29756 |
var useStore = function(api, selector = identity) {
|
| 29757 |
+
const slice = import_react3.default.useSyncExternalStore(api.subscribe, () => selector(api.getState()), () => selector(api.getInitialState()));
|
| 29758 |
+
import_react3.default.useDebugValue(slice);
|
| 29759 |
return slice;
|
| 29760 |
};
|
| 29761 |
var identity = (arg) => arg;
|
|
|
|
| 30001 |
var facePoke = new FacePoke;
|
| 30002 |
|
| 30003 |
// node_modules/beautiful-react-hooks/esm/useThrottledCallback.js
|
| 30004 |
+
var import_react6 = __toESM(require_react(), 1);
|
| 30005 |
var import_lodash = __toESM(require_lodash(), 1);
|
| 30006 |
|
| 30007 |
// node_modules/beautiful-react-hooks/esm/useWillUnmount.js
|
| 30008 |
+
var import_react5 = __toESM(require_react(), 1);
|
| 30009 |
|
| 30010 |
// node_modules/beautiful-react-hooks/esm/shared/isFunction.js
|
| 30011 |
var isFunction = (functionToCheck) => typeof functionToCheck === "function" && !!functionToCheck.constructor && !!functionToCheck.call && !!functionToCheck.apply;
|
| 30012 |
var isFunction_default = isFunction;
|
| 30013 |
|
| 30014 |
// node_modules/beautiful-react-hooks/esm/factory/createHandlerSetter.js
|
| 30015 |
+
var import_react4 = __toESM(require_react(), 1);
|
| 30016 |
var createHandlerSetter = (callback) => {
|
| 30017 |
+
const handlerRef = import_react4.useRef(callback);
|
| 30018 |
+
const setHandler = import_react4.useRef((nextCallback) => {
|
| 30019 |
if (typeof nextCallback !== "function") {
|
| 30020 |
throw new Error("the argument supplied to the \'setHandler\' function should be of type function");
|
| 30021 |
}
|
|
|
|
| 30027 |
|
| 30028 |
// node_modules/beautiful-react-hooks/esm/useWillUnmount.js
|
| 30029 |
var useWillUnmount = (callback) => {
|
| 30030 |
+
const mountRef = import_react5.useRef(false);
|
| 30031 |
const [handler, setHandler] = createHandlerSetter_default(callback);
|
| 30032 |
+
import_react5.useLayoutEffect(() => {
|
| 30033 |
mountRef.current = true;
|
| 30034 |
return () => {
|
| 30035 |
if (isFunction_default(handler === null || handler === undefined ? undefined : handler.current) && mountRef.current) {
|
|
|
|
| 30047 |
trailing: true
|
| 30048 |
};
|
| 30049 |
var useThrottledCallback = (fn2, dependencies, wait = 600, options = defaultOptions) => {
|
| 30050 |
+
const throttled = import_react6.useRef(import_lodash.default(fn2, wait, options));
|
| 30051 |
+
import_react6.useEffect(() => {
|
| 30052 |
throttled.current = import_lodash.default(fn2, wait, options);
|
| 30053 |
}, [fn2, wait, options]);
|
| 30054 |
useWillUnmount_default(() => {
|
| 30055 |
var _a2;
|
| 30056 |
(_a2 = throttled.current) === null || _a2 === undefined || _a2.cancel();
|
| 30057 |
});
|
| 30058 |
+
return import_react6.useCallback(throttled.current, dependencies !== null && dependencies !== undefined ? dependencies : []);
|
| 30059 |
};
|
| 30060 |
var useThrottledCallback_default = useThrottledCallback;
|
| 30061 |
|
|
|
|
| 32830 |
const resetImage = useMainStore((s2) => s2.resetImage);
|
| 32831 |
window.debugJuju = useMainStore;
|
| 32832 |
const averageLatency = 220;
|
| 32833 |
+
const [faceLandmarks, setFaceLandmarks] = import_react7.useState([]);
|
| 32834 |
+
const [isMediaPipeReady, setIsMediaPipeReady] = import_react7.useState(false);
|
| 32835 |
+
const [isDrawingUtilsReady, setIsDrawingUtilsReady] = import_react7.useState(false);
|
| 32836 |
+
const [blendShapes, setBlendShapes] = import_react7.useState([]);
|
| 32837 |
+
const [dragStart, setDragStart] = import_react7.useState(null);
|
| 32838 |
+
const [dragEnd, setDragEnd] = import_react7.useState(null);
|
| 32839 |
+
const [isDragging, setIsDragging] = import_react7.useState(false);
|
| 32840 |
+
const [isWaitingForResponse, setIsWaitingForResponse] = import_react7.useState(false);
|
| 32841 |
+
const dragStartRef = import_react7.useRef(null);
|
| 32842 |
+
const currentMousePosRef = import_react7.useRef(null);
|
| 32843 |
+
const lastModifiedImageHashRef = import_react7.useRef(null);
|
| 32844 |
+
const [currentLandmark, setCurrentLandmark] = import_react7.useState(null);
|
| 32845 |
+
const [previousLandmark, setPreviousLandmark] = import_react7.useState(null);
|
| 32846 |
+
const [currentOpacity, setCurrentOpacity] = import_react7.useState(0);
|
| 32847 |
+
const [previousOpacity, setPreviousOpacity] = import_react7.useState(0);
|
| 32848 |
+
const [isHovering, setIsHovering] = import_react7.useState(false);
|
| 32849 |
+
const canvasRef = import_react7.useRef(null);
|
| 32850 |
+
const mediaPipeRef = import_react7.useRef({
|
| 32851 |
faceLandmarker: null,
|
| 32852 |
drawingUtils: null
|
| 32853 |
});
|
| 32854 |
+
const setActiveLandmark = import_react7.useCallback((newLandmark) => {
|
| 32855 |
setPreviousLandmark(currentLandmark || null);
|
| 32856 |
setCurrentLandmark(newLandmark || null);
|
| 32857 |
setCurrentOpacity(0);
|
| 32858 |
setPreviousOpacity(1);
|
| 32859 |
}, [currentLandmark, setPreviousLandmark, setCurrentLandmark, setCurrentOpacity, setPreviousOpacity]);
|
| 32860 |
+
import_react7.useEffect(() => {
|
| 32861 |
console.log("Initializing MediaPipe...");
|
| 32862 |
let isMounted = true;
|
| 32863 |
const initializeMediaPipe = async () => {
|
|
|
|
| 32895 |
}
|
| 32896 |
};
|
| 32897 |
}, []);
|
| 32898 |
+
const [landmarkCenters, setLandmarkCenters] = import_react7.useState({});
|
| 32899 |
+
const computeLandmarkCenters = import_react7.useCallback((landmarks2) => {
|
| 32900 |
const centers = {};
|
| 32901 |
const computeGroupCenter = (group) => {
|
| 32902 |
let sumX = 0, sumY = 0, sumZ = 0, count = 0;
|
|
|
|
| 32919 |
centers.background = { x: 0.5, y: 0.5, z: 0 };
|
| 32920 |
setLandmarkCenters(centers);
|
| 32921 |
}, []);
|
| 32922 |
+
const findClosestLandmark = import_react7.useCallback((mouseX, mouseY, isGroup) => {
|
| 32923 |
const defaultLandmark = {
|
| 32924 |
group: "background",
|
| 32925 |
distance: 0,
|
|
|
|
| 32968 |
return defaultLandmark;
|
| 32969 |
}
|
| 32970 |
}, [landmarkCenters]);
|
| 32971 |
+
const detectFaceLandmarks = import_react7.useCallback(async (imageDataUrl) => {
|
| 32972 |
if (!isMediaPipeReady) {
|
| 32973 |
console.log("MediaPipe not ready. Skipping detection.");
|
| 32974 |
return;
|
|
|
|
| 32994 |
drawLandmarks(faceLandmarkerResult.faceLandmarks[0], canvasRef.current, drawingUtils);
|
| 32995 |
}
|
| 32996 |
}, [isMediaPipeReady, isDrawingUtilsReady, computeLandmarkCenters]);
|
| 32997 |
+
const drawLandmarks = import_react7.useCallback((landmarks2, canvas, drawingUtils) => {
|
| 32998 |
const ctx = canvas.getContext("2d");
|
| 32999 |
if (!ctx)
|
| 33000 |
return;
|
|
|
|
| 33020 |
img.src = previewImage;
|
| 33021 |
}
|
| 33022 |
}, [previewImage, currentLandmark, previousLandmark, currentOpacity, previousOpacity]);
|
| 33023 |
+
import_react7.useEffect(() => {
|
| 33024 |
if (isMediaPipeReady && isDrawingUtilsReady && faceLandmarks.length > 0 && canvasRef.current && mediaPipeRef.current.drawingUtils) {
|
| 33025 |
drawLandmarks(faceLandmarks[0], canvasRef.current, mediaPipeRef.current.drawingUtils);
|
| 33026 |
}
|
| 33027 |
}, [isMediaPipeReady, isDrawingUtilsReady, faceLandmarks, currentLandmark, previousLandmark, currentOpacity, previousOpacity, drawLandmarks]);
|
| 33028 |
+
import_react7.useEffect(() => {
|
| 33029 |
let animationFrame;
|
| 33030 |
const animate = () => {
|
| 33031 |
setCurrentOpacity((prev) => Math.min(prev + 0.2, 1));
|
|
|
|
| 33037 |
animationFrame = requestAnimationFrame(animate);
|
| 33038 |
return () => cancelAnimationFrame(animationFrame);
|
| 33039 |
}, [currentLandmark]);
|
| 33040 |
+
const canvasRefCallback = import_react7.useCallback((node) => {
|
| 33041 |
if (node !== null) {
|
| 33042 |
const ctx = node.getContext("2d");
|
| 33043 |
if (ctx) {
|
|
|
|
| 33053 |
canvasRef.current = node;
|
| 33054 |
}
|
| 33055 |
}, []);
|
| 33056 |
+
import_react7.useEffect(() => {
|
| 33057 |
if (!isMediaPipeReady) {
|
| 33058 |
console.log("MediaPipe not ready. Skipping landmark detection.");
|
| 33059 |
return;
|
|
|
|
| 33068 |
}
|
| 33069 |
detectFaceLandmarks(previewImage);
|
| 33070 |
}, [isMediaPipeReady, isDrawingUtilsReady, previewImage]);
|
| 33071 |
+
const modifyImage = import_react7.useCallback(({ landmark, vector }) => {
|
| 33072 |
const {
|
| 33073 |
originalImage: originalImage2,
|
| 33074 |
originalImageHash: originalImageHash2,
|
|
|
|
| 33159 |
const modifyImageWithRateLimit = useThrottledCallback_default((params) => {
|
| 33160 |
modifyImage(params);
|
| 33161 |
}, [modifyImage], averageLatency);
|
| 33162 |
+
const handleMouseEnter = import_react7.useCallback(() => {
|
| 33163 |
setIsHovering(true);
|
| 33164 |
}, []);
|
| 33165 |
+
const handleMouseLeave = import_react7.useCallback(() => {
|
| 33166 |
setIsHovering(false);
|
| 33167 |
}, []);
|
| 33168 |
+
const handleMouseDown = import_react7.useCallback((event) => {
|
| 33169 |
if (!canvasRef.current)
|
| 33170 |
return;
|
| 33171 |
const rect = canvasRef.current.getBoundingClientRect();
|
|
|
|
| 33177 |
setDragStart({ x: x2, y: y2 });
|
| 33178 |
dragStartRef.current = { x: x2, y: y2 };
|
| 33179 |
}, [findClosestLandmark, setActiveLandmark, setDragStart]);
|
| 33180 |
+
const handleMouseMove = import_react7.useCallback((event) => {
|
| 33181 |
if (!canvasRef.current)
|
| 33182 |
return;
|
| 33183 |
const rect = canvasRef.current.getBoundingClientRect();
|
|
|
|
| 33203 |
setIsHovering(true);
|
| 33204 |
}
|
| 33205 |
}, [currentLandmark, dragStart, setIsHovering, setActiveLandmark, setIsDragging, modifyImageWithRateLimit, landmarkCenters]);
|
| 33206 |
+
const handleMouseUp = import_react7.useCallback((event) => {
|
| 33207 |
if (!canvasRef.current)
|
| 33208 |
return;
|
| 33209 |
const rect = canvasRef.current.getBoundingClientRect();
|
|
|
|
| 33225 |
dragStartRef.current = null;
|
| 33226 |
setActiveLandmark(undefined);
|
| 33227 |
}, [currentLandmark, isDragging, modifyImageWithRateLimit, findClosestLandmark, setActiveLandmark, landmarkCenters, modifyImageWithRateLimit, setIsDragging]);
|
| 33228 |
+
import_react7.useEffect(() => {
|
| 33229 |
facePoke.setOnModifiedImage((image, image_hash) => {
|
| 33230 |
if (image) {
|
| 33231 |
setPreviewImage(image);
|
|
|
|
| 33261 |
className: "flex flex-row items-center justify-center font-sans mt-4 w-full",
|
| 33262 |
children: [
|
| 33263 |
jsx_dev_runtime2.jsxDEV("span", {
|
| 33264 |
+
className: "mr-1",
|
| 33265 |
children: jsx_dev_runtime2.jsxDEV("img", {
|
| 33266 |
src: "/hf-logo.svg",
|
| 33267 |
alt: "Hugging Face",
|
|
|
|
| 33295 |
}
|
| 33296 |
|
| 33297 |
// src/hooks/useFacePokeAPI.ts
|
| 33298 |
+
var import_react8 = __toESM(require_react(), 1);
|
| 33299 |
function useFacePokeAPI() {
|
| 33300 |
+
const [status, setStatus] = import_react8.useState("");
|
| 33301 |
+
const [isDebugMode, setIsDebugMode] = import_react8.useState(false);
|
| 33302 |
+
const [interruptMessage, setInterruptMessage] = import_react8.useState(null);
|
| 33303 |
+
const [isLoading, setIsLoading] = import_react8.useState(false);
|
| 33304 |
+
import_react8.useEffect(() => {
|
| 33305 |
const urlParams = new URLSearchParams(window.location.search);
|
| 33306 |
setIsDebugMode(urlParams.get("debug") === "true");
|
| 33307 |
}, []);
|
| 33308 |
+
import_react8.useEffect(() => {
|
| 33309 |
const handleInterruption = (event) => {
|
| 33310 |
setInterruptMessage(event.detail.message);
|
| 33311 |
};
|
|
|
|
| 33372 |
const previewImage = useMainStore((s2) => s2.previewImage);
|
| 33373 |
const setPreviewImage = useMainStore((s2) => s2.setPreviewImage);
|
| 33374 |
const resetImage = useMainStore((s2) => s2.resetImage);
|
| 33375 |
+
const setOriginalImageHash = useMainStore((s2) => s2.setOriginalImageHash);
|
| 33376 |
const {
|
| 33377 |
status,
|
| 33378 |
setStatus,
|
|
|
|
| 33396 |
handleMouseLeave,
|
| 33397 |
currentOpacity
|
| 33398 |
} = useFaceLandmarkDetection();
|
| 33399 |
+
const videoRef = import_react9.useRef(null);
|
| 33400 |
+
const handleFileChange = import_react9.useCallback(async (event) => {
|
| 33401 |
const files = event.target.files;
|
| 33402 |
if (files && files[0]) {
|
| 33403 |
setImageFile(files[0]);
|
|
|
|
| 33406 |
const image = await convertImageToBase64(files[0]);
|
| 33407 |
setPreviewImage(image);
|
| 33408 |
setOriginalImage(image);
|
| 33409 |
+
setOriginalImageHash("");
|
| 33410 |
} catch (err) {
|
| 33411 |
console.log(`failed to convert the image: `, err);
|
| 33412 |
setImageFile(null);
|
| 33413 |
setStatus("");
|
| 33414 |
setPreviewImage("");
|
| 33415 |
setOriginalImage("");
|
| 33416 |
+
setOriginalImageHash("");
|
| 33417 |
setFaceLandmarks([]);
|
| 33418 |
setBlendShapes([]);
|
| 33419 |
}
|
|
|
|
| 33422 |
setStatus("");
|
| 33423 |
setPreviewImage("");
|
| 33424 |
setOriginalImage("");
|
| 33425 |
+
setOriginalImageHash("");
|
| 33426 |
setFaceLandmarks([]);
|
| 33427 |
setBlendShapes([]);
|
| 33428 |
}
|
| 33429 |
+
}, [isMediaPipeReady, setImageFile, setPreviewImage, setOriginalImage, setOriginalImageHash, setFaceLandmarks, setBlendShapes, setStatus]);
|
| 33430 |
+
const handleDownload = import_react9.useCallback(() => {
|
| 33431 |
+
if (previewImage) {
|
| 33432 |
+
const link = document.createElement("a");
|
| 33433 |
+
link.href = previewImage;
|
| 33434 |
+
link.download = "modified_image.png";
|
| 33435 |
+
document.body.appendChild(link);
|
| 33436 |
+
link.click();
|
| 33437 |
+
document.body.removeChild(link);
|
| 33438 |
+
}
|
| 33439 |
+
}, [previewImage]);
|
| 33440 |
const canDisplayBlendShapes = false;
|
| 33441 |
+
const displayBlendShapes = import_react9.useMemo(() => jsx_dev_runtime5.jsxDEV("div", {
|
| 33442 |
className: "mt-4",
|
| 33443 |
children: [
|
| 33444 |
jsx_dev_runtime5.jsxDEV("h3", {
|
|
|
|
| 33500 |
className: "flex flex-row items-center justify-between w-full",
|
| 33501 |
children: [
|
| 33502 |
jsx_dev_runtime5.jsxDEV("div", {
|
| 33503 |
+
className: "flex items-center space-x-2",
|
| 33504 |
children: [
|
| 33505 |
+
jsx_dev_runtime5.jsxDEV("div", {
|
| 33506 |
+
className: "relative",
|
| 33507 |
+
children: [
|
| 33508 |
+
jsx_dev_runtime5.jsxDEV("input", {
|
| 33509 |
+
id: "imageInput",
|
| 33510 |
+
type: "file",
|
| 33511 |
+
accept: "image/*",
|
| 33512 |
+
onChange: handleFileChange,
|
| 33513 |
+
className: "hidden",
|
| 33514 |
+
disabled: !isMediaPipeReady
|
| 33515 |
+
}, undefined, false, undefined, this),
|
| 33516 |
+
jsx_dev_runtime5.jsxDEV("label", {
|
| 33517 |
+
htmlFor: "imageInput",
|
| 33518 |
+
className: `cursor-pointer inline-flex items-center px-3 h-10 border border-transparent text-sm font-medium rounded-md text-white ${isMediaPipeReady ? "bg-slate-600 hover:bg-slate-500" : "bg-slate-500 cursor-not-allowed"} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 shadow-xl`,
|
| 33519 |
+
children: [
|
| 33520 |
+
jsx_dev_runtime5.jsxDEV(Spinner, {}, undefined, false, undefined, this),
|
| 33521 |
+
imageFile ? truncateFileName(imageFile.name, 32) : isMediaPipeReady ? "Choose a portrait photo (.jpg, .png, .webp)" : "Initializing..."
|
| 33522 |
+
]
|
| 33523 |
+
}, undefined, true, undefined, this)
|
| 33524 |
+
]
|
| 33525 |
+
}, undefined, true, undefined, this),
|
| 33526 |
+
previewImage && jsx_dev_runtime5.jsxDEV("button", {
|
| 33527 |
+
onClick: handleDownload,
|
| 33528 |
+
className: "inline-flex items-center px-3 h-10 border border-transparent text-sm font-medium rounded-md text-white bg-zinc-600 hover:bg-zinc-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-zinc-500 shadow-xl",
|
| 33529 |
children: [
|
| 33530 |
+
jsx_dev_runtime5.jsxDEV(Download, {
|
| 33531 |
+
className: "w-4 h-4 mr-2"
|
| 33532 |
+
}, undefined, false, undefined, this),
|
| 33533 |
+
"Download"
|
| 33534 |
]
|
| 33535 |
}, undefined, true, undefined, this)
|
| 33536 |
]
|