diff --git a/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/LivePortrait.tsx b/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/LivePortrait.tsx
index 9da9409308..fd8b800860 100644
--- a/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/LivePortrait.tsx
+++ b/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/LivePortrait.tsx
@@ -46,6 +46,7 @@ import SessionLpInferenceResultsList from "./SessionLpInferenceResultsList";
import { GetMedia } from "@storyteller/components/src/api/media_files/GetMedia";
import { useLocation } from "react-router-dom";
import { LivePortraitDetails } from "@storyteller/components/src/api/model_inference/GetModelInferenceJobStatus";
+import { useDocumentTitle } from "@storyteller/components/src/hooks/UseDocumentTitle";
interface LivePortraitProps {
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
@@ -64,6 +65,7 @@ interface GeneratedVideo {
export default function LivePortrait({
sessionSubscriptionsWrapper,
}: LivePortraitProps) {
+ useDocumentTitle("Live Portrait AI. Free Video Animation");
const { enqueueInferenceJob } = useInferenceJobs();
const { open, close } = useModal();
const [isEnqueuing, setIsEnqueuing] = useState(false);
@@ -99,6 +101,9 @@ export default function LivePortrait({
const [currentlyGeneratingList, setCurrentlyGeneratingList] = useState<
{ sourceIndex: number; motionIndex: number }[]
>([]);
+ const [jobProgressPercentage, setJobProgressPercentage] = useState<
+ number | null
+ >(null);
const location = useLocation();
@@ -146,6 +151,15 @@ export default function LivePortrait({
}
};
+ const handleJobProgress = (progress: number | null) => {
+ setJobProgressPercentage(prevProgress => {
+ if (prevProgress !== progress) {
+ return progress;
+ }
+ return prevProgress;
+ });
+ };
+
const handleSourceSelect = (index: number) => {
setIsUserUploaded(index >= numberOfInitialSourceTokens);
setSelectedSourceIndex(index);
@@ -201,6 +215,10 @@ export default function LivePortrait({
});
};
+ useEffect(() => {
+ console.log("Updated jobProgressPercentage:", jobProgressPercentage);
+ }, [jobProgressPercentage]);
+
const renderVideoOrPlaceholder = () => {
// Check if the current combination is being generated
const isCurrentlyGenerating = currentlyGeneratingList.some(
@@ -225,7 +243,9 @@ export default function LivePortrait({
- Generating video...
+ {jobProgressPercentage !== null
+ ? `Generating video... ${jobProgressPercentage}%`
+ : "Generating video..."}
@@ -238,7 +258,7 @@ export default function LivePortrait({
);
} else if (generatedVideoSrc) {
- // Show dynamically generated video if available
+ // Show generated video if available
return (
);
} else {
- // Show "Click to Animate" if nothing is generating or precomputed
+ // Show "Click to Animate" if nothing is generating or samples
return (
@@ -705,6 +726,7 @@ export default function LivePortrait({
stepNumber={2}
onUploadClick={handleOpenUploadMotionModal}
uploadFocusPoint={uploadFocusPointMotion}
+ uploadButtonText="Upload your motion video"
/>
@@ -822,6 +844,7 @@ export default function LivePortrait({
)
}
onJobClick={handleJobClick}
+ onJobProgress={handleJobProgress}
/>
diff --git a/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/SessionLpInferenceResultsList.tsx b/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/SessionLpInferenceResultsList.tsx
index 08602b61fa..7be884d668 100644
--- a/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/SessionLpInferenceResultsList.tsx
+++ b/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/SessionLpInferenceResultsList.tsx
@@ -25,6 +25,7 @@ interface SessionLpInferenceResultsListProps {
addSourceToken: (token: string) => void;
addMotionToken: (token: string) => void;
onJobClick: (job: InferenceJob) => void;
+ onJobProgress: (progress: number | null) => void;
}
export default function SessionLpInferenceResultsList({
@@ -33,10 +34,13 @@ export default function SessionLpInferenceResultsList({
addMotionToken,
addSourceToken,
onJobClick,
+ onJobProgress,
}: SessionLpInferenceResultsListProps) {
const { inferenceJobsByCategory } = useInferenceJobs();
const hasInitialized = useRef(false);
+ const lastProgressRef = useRef<{ [key: string]: number | null }>({});
+
const livePortraitJobs = useMemo(() => {
return (
inferenceJobsByCategory.get(FrontendInferenceJobType.LivePortrait) || []
@@ -77,6 +81,34 @@ export default function SessionLpInferenceResultsList({
}
}, [livePortraitJobs, onJobTokens, addSourceToken, addMotionToken]);
+ useEffect(() => {
+ livePortraitJobs.forEach((job: InferenceJob) => {
+ const currentProgress = job.progressPercentage;
+
+ if (job.jobState === JobState.STARTED && currentProgress !== null) {
+ if (lastProgressRef.current[job.jobToken] !== currentProgress) {
+ console.log(
+ `Updating progress for Job ${job.jobToken}: ${currentProgress}%`
+ );
+ onJobProgress(currentProgress);
+ lastProgressRef.current[job.jobToken] = currentProgress;
+ }
+ }
+
+ if (
+ (job.jobState === JobState.COMPLETE_SUCCESS ||
+ job.jobState === JobState.COMPLETE_FAILURE) &&
+ lastProgressRef.current[job.jobToken] !== null
+ ) {
+ console.log(
+ `Job ${job.jobToken} is complete. Resetting progress to null.`
+ );
+ onJobProgress(null);
+ lastProgressRef.current[job.jobToken] = null;
+ }
+ });
+ }, [livePortraitJobs, onJobProgress]);
+
const [mediaSrc, setMediaSrc] = useState<{ [key: string]: string }>({});
useEffect(() => {
@@ -166,7 +198,9 @@ export default function SessionLpInferenceResultsList({
More Details
) : (
- Waiting...
+
+ {job.progressPercentage}% complete
+
)}
diff --git a/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/ThumbnailMediaPicker.tsx b/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/ThumbnailMediaPicker.tsx
index 990de32c4f..fdd1829064 100644
--- a/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/ThumbnailMediaPicker.tsx
+++ b/src/website/packages/fakeyou.com/src/v2/view/pages/live_portrait/ThumbnailMediaPicker.tsx
@@ -36,6 +36,7 @@ interface ThumbnailMediaPickerProps {
onUploadClick?: () => void;
onSelectedMediaChange?: (media: any) => void;
uploadFocusPoint?: boolean;
+ uploadButtonText?: string;
}
interface MediaData {
@@ -58,6 +59,7 @@ const ThumbnailMediaPicker: React.FC = React.memo(
onUploadClick,
onSelectedMediaChange,
uploadFocusPoint,
+ uploadButtonText = "Upload your own media",
}) => {
const [zoom, setZoom] = useState(1);
const [isCropping, setIsCropping] = useState(false);
@@ -289,8 +291,8 @@ const ThumbnailMediaPicker: React.FC = React.memo(