Spaces:
Running
on
L40S
Running
on
L40S
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 |
]
|