diff --git a/package-lock.json b/package-lock.json index 3a4a0d87cb..b2e681900c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "react-colorful": "^5.6.1", "react-dom": "^18.3.1", "react-drag-drop-files": "^2.3.10", - "react-easy-crop": "^5.0.8", + "react-image-crop": "^11.0.7", "react-router-dom": "^6.26.1", "tailwind-merge": "^2.5.2", "uuid": "^10.0.0", @@ -4102,11 +4102,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-wheel": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz", - "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==" - }, "node_modules/npm": { "version": "10.8.3", "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.3.tgz", @@ -7737,17 +7732,12 @@ "react-dom": "^18.0.0" } }, - "node_modules/react-easy-crop": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.8.tgz", - "integrity": "sha512-KjulxXhR5iM7+ATN2sGCum/IyDxGw7xT0dFoGcqUP+ysaPU5Ka7gnrDa2tUHFHUoMNyPrVZ05QA+uvMgC5ym/g==", - "dependencies": { - "normalize-wheel": "^1.0.1", - "tslib": "^2.0.1" - }, + "node_modules/react-image-crop": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/react-image-crop/-/react-image-crop-11.0.7.tgz", + "integrity": "sha512-ZciKWHDYzmm366JDL18CbrVyjnjH0ojufGDmScfS4ZUqLHg4nm6ATY+K62C75W4ZRNt4Ii+tX0bSjNk9LQ2xzQ==", "peerDependencies": { - "react": ">=16.4.0", - "react-dom": ">=16.4.0" + "react": ">=16.13.1" } }, "node_modules/react-is": { diff --git a/package.json b/package.json index 256416d688..36d411dff2 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "react-colorful": "^5.6.1", "react-dom": "^18.3.1", "react-drag-drop-files": "^2.3.10", - "react-easy-crop": "^5.0.8", + "react-easy-crop": "^5.1.0", "react-router-dom": "^6.26.1", "tailwind-merge": "^2.5.2", "uuid": "^10.0.0", diff --git a/src/app/index.tsx b/src/app/index.tsx index 014cecdf75..50e901f099 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -9,20 +9,20 @@ import { PostHogProvider } from "posthog-js/react"; import "./global.css"; import { useRenderCounter } from "~/hooks/useRenderCounter"; -function PostHogInit() { - const apiKey = "phc_jBFgac0mVALAFk3negnSfYCcHgvkT00yBLQDmCDYNBb"; - posthog.init(apiKey, { - api_host: "https://us.i.posthog.com/", - ui_host: "https://us.i.posthog.com/", - }); -} +// function PostHogInit() { +// const apiKey = "phc_jBFgac0mVALAFk3negnSfYCcHgvkT00yBLQDmCDYNBb"; +// posthog.init(apiKey, { +// api_host: "https://us.i.posthog.com/", +// ui_host: "https://us.i.posthog.com/", +// }); +// } const App = () => { const useStrictMode = false; useRenderCounter("App"); - useEffect(() => { - PostHogInit(); - }, []); + // useEffect(() => { + // PostHogInit(); + // }, []); if (useStrictMode) { return ( diff --git a/src/components/features/DialogAiStylize/DialogAiStylize.tsx b/src/components/features/DialogAiStylize/DialogAiStylize.tsx index a783eecd03..6bdf1512f4 100644 --- a/src/components/features/DialogAiStylize/DialogAiStylize.tsx +++ b/src/components/features/DialogAiStylize/DialogAiStylize.tsx @@ -1,10 +1,7 @@ import { useCallback, useEffect, useState } from "react"; import { twMerge } from "tailwind-merge"; import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react"; -import { - faWandSparkles, - faChevronLeft, -} from "@fortawesome/pro-solid-svg-icons"; +import { faWandSparkles } from "@fortawesome/pro-solid-svg-icons"; import { Button } from "~/components/ui"; import { @@ -115,35 +112,25 @@ export const DialogAiStylize = ({ )} style={{ height: "calc(100vh - 200px)" }} > + Use AI to Stylize + {panelState === SubPanelNames.BASIC && ( - <> - Use AI to Stylize - - + )} {panelState === SubPanelNames.ADVANCED && ( - <> - - - + )}
+ +
+ + )} + + {ipaUrl && ( //case of having ipaFileUrl +
+ +
+ IPAdapter +
+
+
+ )} + + ); +}; diff --git a/src/components/features/DialogAiStylize/SubPanelAdvance/SubPanelAdvance.tsx b/src/components/features/DialogAiStylize/SubPanelAdvance/SubPanelAdvance.tsx index 308d8a695f..3c5becb045 100644 --- a/src/components/features/DialogAiStylize/SubPanelAdvance/SubPanelAdvance.tsx +++ b/src/components/features/DialogAiStylize/SubPanelAdvance/SubPanelAdvance.tsx @@ -1,13 +1,19 @@ +import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons"; import { StyleOptionSwitches } from "./StyleOptionSwitches"; import { AIStylizeProps } from "../utilities"; import { StyleStrengthSlider } from "./StyleStrengthSlider"; +import { IPAdapter } from "./IPAdapter"; +import { Button } from "~/components/ui"; +import { SubPanelNames } from "../enums"; export const SubPanelAdvance = ({ aiStylizeProps, onStylizeOptionsChanged, + onChangePanel, }: { aiStylizeProps: AIStylizeProps; onStylizeOptionsChanged: (newOptions: Partial) => void; + onChangePanel: (newP: SubPanelNames) => void; }) => { const { cinematic, @@ -19,11 +25,18 @@ export const SubPanelAdvance = ({ } = aiStylizeProps; return ( -
-
-
IP Adapter
+
+
+ + onStylizeOptionsChanged({ + globalIpaMediaToken: newToken, + }) + } + />
-
+

Advanced Options

+ +
); diff --git a/src/components/features/DialogAiStylize/SubPanelAdvance/utilities.ts b/src/components/features/DialogAiStylize/SubPanelAdvance/utilities.ts new file mode 100644 index 0000000000..efbabff129 --- /dev/null +++ b/src/components/features/DialogAiStylize/SubPanelAdvance/utilities.ts @@ -0,0 +1,158 @@ +import { v4 as uuidv4 } from "uuid"; +import { MediaFilesApi, MediaUploadApi } from "~/Classes/ApiManager"; +import { Area } from "react-easy-crop"; + +export const base64ToFile = (base64: string, filename: string): File => { + const arr = base64.split(","); + const mime = arr[0].match(/:(.*?);/)?.[1]; + const bstr = atob(arr[1]); + let n = bstr.length; + const u8arr = new Uint8Array(n); + + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + + return new File([u8arr], filename, { type: mime }); +}; + +export const uploadIPA = async (file: File) => { + const mediaUploadApi = new MediaUploadApi(); + const response = await mediaUploadApi.UploadImage({ + blob: file, + fileName: file.name, + uuid: uuidv4(), + }); + if (!response.success || !response.data) { + console.error("Error IPA: Fail in uploading image"); + return; + } + return response.data; +}; + +export const fetchIPAUrl = async (ipaToken: string) => { + const mediaFilesApi = new MediaFilesApi(); + try { + const response = await mediaFilesApi.GetMediaFileByToken({ + mediaFileToken: ipaToken, + }); + if (response.success && response.data) { + return response.data.media_links.cdn_url; + } else { + console.error("Failed to fetch media file:", response.errorMessage); + } + } catch (error) { + console.error("Error fetching media file:", error); + } +}; + +export const createImage = (url: string) => + new Promise((resolve, reject) => { + const image = new Image(); + image.addEventListener("load", () => resolve(image)); + image.addEventListener("error", (error) => reject(error)); + image.setAttribute("crossOrigin", "anonymous"); + // needed to avoid cross-origin issues + image.src = url; + }); + +export function getRadianAngle(degreeValue: number) { + return (degreeValue * Math.PI) / 180; +} + +/** + * Returns the new bounding area of a rotated rectangle. + */ +export function rotateSize(width: number, height: number, rotation: number) { + const rotRad = getRadianAngle(rotation); + + return { + width: + Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height), + height: + Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height), + }; +} + +/** + * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop + */ +export default async function getCroppedImg( + imageSrc: string, + pixelCrop: Area, + rotation = 0, + flip = { horizontal: false, vertical: false }, +) { + const image = await createImage(imageSrc); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + if (!ctx) { + return null; + } + + const rotRad = getRadianAngle(rotation); + + // calculate bounding box of the rotated image + const { width: bBoxWidth, height: bBoxHeight } = rotateSize( + image.width, + image.height, + rotation, + ); + + // set canvas size to match the bounding box + canvas.width = bBoxWidth; + canvas.height = bBoxHeight; + + // translate canvas context to a central location to allow rotating and flipping around the center + ctx.translate(bBoxWidth / 2, bBoxHeight / 2); + ctx.rotate(rotRad); + ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1); + ctx.translate(-image.width / 2, -image.height / 2); + + // draw rotated image + ctx.drawImage(image, 0, 0); + + const croppedCanvas = document.createElement("canvas"); + + const croppedCtx = croppedCanvas.getContext("2d"); + + if (!croppedCtx) { + return null; + } + + // Set the size of the cropped canvas + croppedCanvas.width = pixelCrop.width; + croppedCanvas.height = pixelCrop.height; + + // Draw the cropped image onto the new canvas + croppedCtx.drawImage( + canvas, + pixelCrop.x, + pixelCrop.y, + pixelCrop.width, + pixelCrop.height, + 0, + 0, + pixelCrop.width, + pixelCrop.height, + ); + + // As Base64 string + // return croppedCanvas.toDataURL('image/jpeg'); + + // As a blob + return new Promise((resolve, reject) => { + croppedCanvas.toBlob((blob) => { + if (blob) { + const reader = new FileReader(); + reader.readAsDataURL(blob); + reader.onloadend = () => { + resolve(reader.result as string); + }; + } else { + reject(new Error("Canvas is empty")); + } + }, "image/jpeg"); + }); +} diff --git a/src/components/features/DialogAiStylize/SubPanelBasic/ArtStyleItem.tsx b/src/components/features/DialogAiStylize/SubPanelBasic/ArtStyleItem.tsx index 3ac1a562fe..e859615c11 100644 --- a/src/components/features/DialogAiStylize/SubPanelBasic/ArtStyleItem.tsx +++ b/src/components/features/DialogAiStylize/SubPanelBasic/ArtStyleItem.tsx @@ -38,7 +38,7 @@ export const ArtStyleItem = ({ return ( */} +
); diff --git a/src/components/features/DialogAiStylize/utilities.ts b/src/components/features/DialogAiStylize/utilities.ts index fbf00398da..830f2c832f 100644 --- a/src/components/features/DialogAiStylize/utilities.ts +++ b/src/components/features/DialogAiStylize/utilities.ts @@ -5,6 +5,7 @@ import { } from "./data/randomTextList"; export type AIStylizeProps = { + globalIpaMediaToken?: string; selectedArtStyle: ArtStyleNames; positivePrompt: string; negativePrompt: string; diff --git a/yarn.lock b/yarn.lock index 16f0f1f81d..082ce26879 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2221,7 +2221,7 @@ fs-minipass@^3.0.0, fs-minipass@^3.0.3: fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: @@ -3225,7 +3225,7 @@ normalize-range@^0.1.2: normalize-wheel@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz#aec886affdb045070d856447df62ecf86146ec45" integrity sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA== npm-audit-report@^5.0.0: @@ -3779,10 +3779,10 @@ react-drag-drop-files@^2.3.10: prop-types "^15.7.2" styled-components "^5.3.0" -react-easy-crop@^5.0.8: - version "5.0.8" - resolved "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.8.tgz" - integrity sha512-KjulxXhR5iM7+ATN2sGCum/IyDxGw7xT0dFoGcqUP+ysaPU5Ka7gnrDa2tUHFHUoMNyPrVZ05QA+uvMgC5ym/g== +react-easy-crop@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/react-easy-crop/-/react-easy-crop-5.1.0.tgz#79897cbd8fbc93f64817276a02de525582a6334b" + integrity sha512-UsYeF/N7zoqtfOSD+2xSt1nRaoBYCI2YLkzmq+hi+aVepS4/bAMhbrLwJtDAP60jsVzWRiQCX7JG+ZtfWcHsiw== dependencies: normalize-wheel "^1.0.1" tslib "^2.0.1" @@ -4394,7 +4394,12 @@ tsconfck@^3.0.3: resolved "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.1.tgz" integrity sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ== -tslib@^2.0.1, tslib@^2.4.0: +tslib@^2.0.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b" + integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA== + +tslib@^2.4.0: version "2.7.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==