@umodeapporg/ui 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Form/ControlledImage/index.d.ts +3 -0
- package/dist/components/Form/ControlledImage/index.js +2 -1
- package/dist/components/Form/ControlledImage/index.js.map +1 -1
- package/dist/components/UI/DataTable/DataTable.js +15 -4
- package/dist/components/UI/DataTable/DataTable.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableBody.d.ts +5 -1
- package/dist/components/UI/DataTable/DataTableBody.js +15 -2
- package/dist/components/UI/DataTable/DataTableBody.js.map +1 -1
- package/dist/components/UI/DataTable/DataTableBodyCell.js +11 -0
- package/dist/components/UI/DataTable/DataTableBodyCell.js.map +1 -1
- package/dist/components/UI/DataTable/cells/DataTableEditableCell.js +7 -10
- package/dist/components/UI/DataTable/cells/DataTableEditableCell.js.map +1 -1
- package/dist/components/UI/DataTable/grid/GridProvider.d.ts +3 -0
- package/dist/components/UI/DataTable/grid/GridProvider.js +25 -3
- package/dist/components/UI/DataTable/grid/GridProvider.js.map +1 -1
- package/dist/components/UI/DataTable/grid/gridStore.d.ts +10 -2
- package/dist/components/UI/DataTable/grid/gridStore.js +30 -3
- package/dist/components/UI/DataTable/grid/gridStore.js.map +1 -1
- package/dist/components/UI/DataTable/grid/gridTypes.d.ts +17 -5
- package/dist/components/UI/DataTable/grid/gridTypes.js.map +1 -1
- package/dist/components/UI/DataTable/grid/useGridKeyboard.d.ts +4 -2
- package/dist/components/UI/DataTable/grid/useGridKeyboard.js +25 -11
- package/dist/components/UI/DataTable/grid/useGridKeyboard.js.map +1 -1
- package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.d.ts +6 -0
- package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.js +164 -74
- package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.js.map +1 -1
- package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.d.ts +8 -0
- package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.js +67 -0
- package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.js.map +1 -0
- package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.d.ts +8 -0
- package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.js +39 -0
- package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.js.map +1 -0
- package/dist/components/UI/ImageAiAdjust/imageAiReview.d.ts +22 -0
- package/dist/components/UI/ImageAiAdjust/imageAiReview.js +37 -0
- package/dist/components/UI/ImageAiAdjust/imageAiReview.js.map +1 -0
- package/dist/components/UI/ImageAiAdjust/imageAiVerification.d.ts +19 -0
- package/dist/components/UI/ImageAiAdjust/imageAiVerification.js +25 -0
- package/dist/components/UI/ImageAiAdjust/imageAiVerification.js.map +1 -0
- package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.d.ts +25 -2
- package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.js +44 -19
- package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.js.map +1 -1
- package/dist/components/UI/ImageMaskPainter/index.d.ts +19 -0
- package/dist/components/UI/ImageMaskPainter/index.js +267 -0
- package/dist/components/UI/ImageMaskPainter/index.js.map +1 -0
- package/dist/components/UI/ImageMaskPainter/maskStrokes.d.ts +19 -0
- package/dist/components/UI/ImageMaskPainter/maskStrokes.js +71 -0
- package/dist/components/UI/ImageMaskPainter/maskStrokes.js.map +1 -0
- package/dist/helpers/imageFile.d.ts +16 -0
- package/dist/helpers/imageFile.js +59 -10
- package/dist/helpers/imageFile.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -10
- package/dist/index.js.map +1 -1
- package/dist/locales/en/ui.json.d.ts +36 -1
- package/dist/locales/en/ui.json.js +7 -1
- package/dist/locales/en/ui.json.js.map +1 -1
- package/dist/locales/index.d.ts +70 -0
- package/dist/locales/pt/ui.json.d.ts +36 -1
- package/dist/locales/pt/ui.json.js +7 -1
- package/dist/locales/pt/ui.json.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import { useTranslation } from "react-i18next";
|
|
4
|
+
import { MaterialSymbol } from "../MaterialSymbol/index.js";
|
|
5
|
+
const ImageAiVerificationNotice = ({ verification, className }) => {
|
|
6
|
+
const { t } = useTranslation("ui");
|
|
7
|
+
const changedTooMuch = verification?.changedMoreThanRequested === true;
|
|
8
|
+
const colorCheck = verification?.colorCheck ?? null;
|
|
9
|
+
const colorFailed = colorCheck?.passed === false;
|
|
10
|
+
if (!changedTooMuch && !colorFailed) return null;
|
|
11
|
+
return /* @__PURE__ */ jsxs(
|
|
12
|
+
"div",
|
|
13
|
+
{
|
|
14
|
+
role: "status",
|
|
15
|
+
className: classNames(
|
|
16
|
+
"flex flex-col gap-y-1 rounded-xl border border-warning-border bg-warning-soft px-3 py-2 text-xs",
|
|
17
|
+
"text-warning-emphasis",
|
|
18
|
+
className
|
|
19
|
+
),
|
|
20
|
+
children: [
|
|
21
|
+
changedTooMuch && /* @__PURE__ */ jsxs("span", { className: "flex items-start gap-x-1.5", children: [
|
|
22
|
+
/* @__PURE__ */ jsx(MaterialSymbol, { "aria-hidden": true, icon: "warning", size: 14, className: "mt-px shrink-0" }),
|
|
23
|
+
t("imageAiAdjust.verification.changedMoreThanRequested")
|
|
24
|
+
] }),
|
|
25
|
+
colorFailed && colorCheck && /* @__PURE__ */ jsxs("span", { className: "flex items-start gap-x-1.5", children: [
|
|
26
|
+
/* @__PURE__ */ jsx(MaterialSymbol, { "aria-hidden": true, icon: "warning", size: 14, className: "mt-px shrink-0" }),
|
|
27
|
+
t("imageAiAdjust.verification.colorMismatch", {
|
|
28
|
+
dominant: colorCheck.dominantHex,
|
|
29
|
+
target: colorCheck.targetHex
|
|
30
|
+
})
|
|
31
|
+
] })
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
export {
|
|
37
|
+
ImageAiVerificationNotice as default
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=ImageAiVerificationNotice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageAiVerificationNotice.js","sources":["../../../../src/components/UI/ImageAiAdjust/ImageAiVerificationNotice.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\n\nimport { IImageAiVerification } from './imageAiVerification';\n\ninterface IImageAiVerificationNoticeProps {\n verification: IImageAiVerification | null | undefined;\n className?: string;\n}\n\n/** Renders nothing unless the backend flagged the result; the flags are advisory only. */\nconst ImageAiVerificationNotice: React.FC<IImageAiVerificationNoticeProps> = ({ verification, className }) => {\n const { t } = useTranslation('ui');\n\n const changedTooMuch = verification?.changedMoreThanRequested === true;\n const colorCheck = verification?.colorCheck ?? null;\n const colorFailed = colorCheck?.passed === false;\n if (!changedTooMuch && !colorFailed) return null;\n\n return (\n <div\n role=\"status\"\n className={classNames(\n 'flex flex-col gap-y-1 rounded-xl border border-warning-border bg-warning-soft px-3 py-2 text-xs',\n 'text-warning-emphasis',\n className,\n )}\n >\n {changedTooMuch && (\n <span className=\"flex items-start gap-x-1.5\">\n <MaterialSymbol aria-hidden icon=\"warning\" size={14} className=\"mt-px shrink-0\" />\n {t('imageAiAdjust.verification.changedMoreThanRequested')}\n </span>\n )}\n {colorFailed && colorCheck && (\n <span className=\"flex items-start gap-x-1.5\">\n <MaterialSymbol aria-hidden icon=\"warning\" size={14} className=\"mt-px shrink-0\" />\n {t('imageAiAdjust.verification.colorMismatch', {\n dominant: colorCheck.dominantHex,\n target: colorCheck.targetHex,\n })}\n </span>\n )}\n </div>\n );\n};\n\nexport default ImageAiVerificationNotice;\n"],"names":[],"mappings":";;;;AAaA,MAAM,4BAAuE,CAAC,EAAE,cAAc,gBAAgB;AAC5G,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,QAAM,iBAAiB,cAAc,6BAA6B;AAClE,QAAM,aAAa,cAAc,cAAc;AAC/C,QAAM,cAAc,YAAY,WAAW;AAC3C,MAAI,CAAC,kBAAkB,CAAC,YAAa,QAAO;AAE5C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,MAGD,UAAA;AAAA,QAAA,kBACC,qBAAC,QAAA,EAAK,WAAU,8BACd,UAAA;AAAA,UAAA,oBAAC,gBAAA,EAAe,eAAW,MAAC,MAAK,WAAU,MAAM,IAAI,WAAU,iBAAA,CAAiB;AAAA,UAC/E,EAAE,qDAAqD;AAAA,QAAA,GAC1D;AAAA,QAED,eAAe,cACd,qBAAC,QAAA,EAAK,WAAU,8BACd,UAAA;AAAA,UAAA,oBAAC,gBAAA,EAAe,eAAW,MAAC,MAAK,WAAU,MAAM,IAAI,WAAU,iBAAA,CAAiB;AAAA,UAC/E,EAAE,4CAA4C;AAAA,YAC7C,UAAU,WAAW;AAAA,YACrB,QAAQ,WAAW;AAAA,UAAA,CACpB;AAAA,QAAA,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR;"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Why a user discarded an AI result; mirrors the backend ReviewReasonCode. */
|
|
2
|
+
export declare const IMAGE_AI_REVIEW_REASON_CODES: readonly ["changed_the_product", "wrong_color", "bad_quality", "person_changed", "text_or_logo", "not_what_i_asked", "other"];
|
|
3
|
+
export type TImageAiReviewReasonCode = (typeof IMAGE_AI_REVIEW_REASON_CODES)[number];
|
|
4
|
+
export type TImageAiReviewDecision = 'approved' | 'discarded';
|
|
5
|
+
/** Matches the backend limit for the free-text review reason. */
|
|
6
|
+
export declare const IMAGE_AI_REVIEW_REASON_MAX = 500;
|
|
7
|
+
/** Keys in the `ui` namespace, so hosts can render the same labels. */
|
|
8
|
+
export declare const IMAGE_AI_REVIEW_REASON_LABEL_KEYS: Record<TImageAiReviewReasonCode, string>;
|
|
9
|
+
export declare const isImageAiReviewReasonCode: (value: unknown) => value is TImageAiReviewReasonCode;
|
|
10
|
+
export interface IImageAiDiscardReason {
|
|
11
|
+
reasonCode: TImageAiReviewReasonCode;
|
|
12
|
+
reason?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IImageAiReview {
|
|
15
|
+
generationId: string;
|
|
16
|
+
decision: TImageAiReviewDecision;
|
|
17
|
+
reasonCode?: TImageAiReviewReasonCode;
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
export type TImageAiReviewHandler = (review: IImageAiReview) => Promise<void> | void;
|
|
21
|
+
/** A review is feedback, never a gate: failures are logged and swallowed. */
|
|
22
|
+
export declare const submitImageAiReview: (handler: TImageAiReviewHandler | undefined, review: IImageAiReview) => Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { uiLogger } from "../../../helpers/uiLogger.js";
|
|
2
|
+
const IMAGE_AI_REVIEW_REASON_CODES = [
|
|
3
|
+
"changed_the_product",
|
|
4
|
+
"wrong_color",
|
|
5
|
+
"bad_quality",
|
|
6
|
+
"person_changed",
|
|
7
|
+
"text_or_logo",
|
|
8
|
+
"not_what_i_asked",
|
|
9
|
+
"other"
|
|
10
|
+
];
|
|
11
|
+
const IMAGE_AI_REVIEW_REASON_MAX = 500;
|
|
12
|
+
const IMAGE_AI_REVIEW_REASON_LABEL_KEYS = {
|
|
13
|
+
changed_the_product: "imageAiReview.reasons.changed_the_product",
|
|
14
|
+
wrong_color: "imageAiReview.reasons.wrong_color",
|
|
15
|
+
bad_quality: "imageAiReview.reasons.bad_quality",
|
|
16
|
+
person_changed: "imageAiReview.reasons.person_changed",
|
|
17
|
+
text_or_logo: "imageAiReview.reasons.text_or_logo",
|
|
18
|
+
not_what_i_asked: "imageAiReview.reasons.not_what_i_asked",
|
|
19
|
+
other: "imageAiReview.reasons.other"
|
|
20
|
+
};
|
|
21
|
+
const isImageAiReviewReasonCode = (value) => typeof value === "string" && IMAGE_AI_REVIEW_REASON_CODES.includes(value);
|
|
22
|
+
const submitImageAiReview = async (handler, review) => {
|
|
23
|
+
if (!handler || !review.generationId) return;
|
|
24
|
+
try {
|
|
25
|
+
await handler(review);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
uiLogger.warn("Failed to submit the AI image review", error);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
IMAGE_AI_REVIEW_REASON_CODES,
|
|
32
|
+
IMAGE_AI_REVIEW_REASON_LABEL_KEYS,
|
|
33
|
+
IMAGE_AI_REVIEW_REASON_MAX,
|
|
34
|
+
isImageAiReviewReasonCode,
|
|
35
|
+
submitImageAiReview
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=imageAiReview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imageAiReview.js","sources":["../../../../src/components/UI/ImageAiAdjust/imageAiReview.ts"],"sourcesContent":["import { uiLogger } from '@/helpers/uiLogger';\n\n/** Why a user discarded an AI result; mirrors the backend ReviewReasonCode. */\nexport const IMAGE_AI_REVIEW_REASON_CODES = [\n 'changed_the_product',\n 'wrong_color',\n 'bad_quality',\n 'person_changed',\n 'text_or_logo',\n 'not_what_i_asked',\n 'other',\n] as const;\n\nexport type TImageAiReviewReasonCode = (typeof IMAGE_AI_REVIEW_REASON_CODES)[number];\n\nexport type TImageAiReviewDecision = 'approved' | 'discarded';\n\n/** Matches the backend limit for the free-text review reason. */\nexport const IMAGE_AI_REVIEW_REASON_MAX = 500;\n\n/** Keys in the `ui` namespace, so hosts can render the same labels. */\nexport const IMAGE_AI_REVIEW_REASON_LABEL_KEYS: Record<TImageAiReviewReasonCode, string> = {\n changed_the_product: 'imageAiReview.reasons.changed_the_product',\n wrong_color: 'imageAiReview.reasons.wrong_color',\n bad_quality: 'imageAiReview.reasons.bad_quality',\n person_changed: 'imageAiReview.reasons.person_changed',\n text_or_logo: 'imageAiReview.reasons.text_or_logo',\n not_what_i_asked: 'imageAiReview.reasons.not_what_i_asked',\n other: 'imageAiReview.reasons.other',\n};\n\nexport const isImageAiReviewReasonCode = (value: unknown): value is TImageAiReviewReasonCode =>\n typeof value === 'string' && (IMAGE_AI_REVIEW_REASON_CODES as ReadonlyArray<string>).includes(value);\n\nexport interface IImageAiDiscardReason {\n reasonCode: TImageAiReviewReasonCode;\n reason?: string;\n}\n\nexport interface IImageAiReview {\n generationId: string;\n decision: TImageAiReviewDecision;\n reasonCode?: TImageAiReviewReasonCode;\n reason?: string;\n}\n\nexport type TImageAiReviewHandler = (review: IImageAiReview) => Promise<void> | void;\n\n/** A review is feedback, never a gate: failures are logged and swallowed. */\nexport const submitImageAiReview = async (\n handler: TImageAiReviewHandler | undefined,\n review: IImageAiReview,\n): Promise<void> => {\n if (!handler || !review.generationId) return;\n\n try {\n await handler(review);\n } catch (error) {\n uiLogger.warn('Failed to submit the AI image review', error);\n }\n};\n"],"names":[],"mappings":";AAGO,MAAM,+BAA+B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,MAAM,6BAA6B;AAGnC,MAAM,oCAA8E;AAAA,EACzF,qBAAqB;AAAA,EACrB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,OAAO;AACT;AAEO,MAAM,4BAA4B,CAAC,UACxC,OAAO,UAAU,YAAa,6BAAuD,SAAS,KAAK;AAiB9F,MAAM,sBAAsB,OACjC,SACA,WACkB;AAClB,MAAI,CAAC,WAAW,CAAC,OAAO,aAAc;AAEtC,MAAI;AACF,UAAM,QAAQ,MAAM;AAAA,EACtB,SAAS,OAAO;AACd,aAAS,KAAK,wCAAwC,KAAK;AAAA,EAC7D;AACF;"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Backend check of the dominant garment colour against the requested one (CIEDE2000). */
|
|
2
|
+
export interface IImageAiColorCheck {
|
|
3
|
+
targetHex: string;
|
|
4
|
+
dominantHex: string;
|
|
5
|
+
deltaE: number;
|
|
6
|
+
passed: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** What the backend measured on an AI result; every field is advisory, never blocking. */
|
|
9
|
+
export interface IImageAiVerification {
|
|
10
|
+
/** Mean normalized difference outside the painted area (whole image without a mask), 0..1. */
|
|
11
|
+
changeScore: number | null;
|
|
12
|
+
threshold: number;
|
|
13
|
+
changedMoreThanRequested: boolean;
|
|
14
|
+
/** True when pixels outside the mask were restored from the source. */
|
|
15
|
+
composited: boolean;
|
|
16
|
+
colorCheck: IImageAiColorCheck | null;
|
|
17
|
+
}
|
|
18
|
+
/** Coerces whatever the host received into the verification shape, or null when absent/garbled. */
|
|
19
|
+
export declare const normalizeImageAiVerification: (value: unknown) => IImageAiVerification | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
2
|
+
const finiteOr = (value, fallback) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
3
|
+
const normalizeColorCheck = (value) => {
|
|
4
|
+
if (!isRecord(value) || typeof value.passed !== "boolean") return null;
|
|
5
|
+
return {
|
|
6
|
+
targetHex: typeof value.targetHex === "string" ? value.targetHex : "",
|
|
7
|
+
dominantHex: typeof value.dominantHex === "string" ? value.dominantHex : "",
|
|
8
|
+
deltaE: finiteOr(value.deltaE, 0),
|
|
9
|
+
passed: value.passed
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
const normalizeImageAiVerification = (value) => {
|
|
13
|
+
if (!isRecord(value)) return null;
|
|
14
|
+
return {
|
|
15
|
+
changeScore: finiteOr(value.changeScore, null),
|
|
16
|
+
threshold: finiteOr(value.threshold, 0),
|
|
17
|
+
changedMoreThanRequested: value.changedMoreThanRequested === true,
|
|
18
|
+
composited: value.composited === true,
|
|
19
|
+
colorCheck: normalizeColorCheck(value.colorCheck)
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export {
|
|
23
|
+
normalizeImageAiVerification
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=imageAiVerification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imageAiVerification.js","sources":["../../../../src/components/UI/ImageAiAdjust/imageAiVerification.ts"],"sourcesContent":["/** Backend check of the dominant garment colour against the requested one (CIEDE2000). */\nexport interface IImageAiColorCheck {\n targetHex: string;\n dominantHex: string;\n deltaE: number;\n passed: boolean;\n}\n\n/** What the backend measured on an AI result; every field is advisory, never blocking. */\nexport interface IImageAiVerification {\n /** Mean normalized difference outside the painted area (whole image without a mask), 0..1. */\n changeScore: number | null;\n threshold: number;\n changedMoreThanRequested: boolean;\n /** True when pixels outside the mask were restored from the source. */\n composited: boolean;\n colorCheck: IImageAiColorCheck | null;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null;\n\nconst finiteOr = <T>(value: unknown, fallback: T): number | T =>\n typeof value === 'number' && Number.isFinite(value) ? value : fallback;\n\nconst normalizeColorCheck = (value: unknown): IImageAiColorCheck | null => {\n if (!isRecord(value) || typeof value.passed !== 'boolean') return null;\n return {\n targetHex: typeof value.targetHex === 'string' ? value.targetHex : '',\n dominantHex: typeof value.dominantHex === 'string' ? value.dominantHex : '',\n deltaE: finiteOr(value.deltaE, 0),\n passed: value.passed,\n };\n};\n\n/** Coerces whatever the host received into the verification shape, or null when absent/garbled. */\nexport const normalizeImageAiVerification = (value: unknown): IImageAiVerification | null => {\n if (!isRecord(value)) return null;\n return {\n changeScore: finiteOr(value.changeScore, null),\n threshold: finiteOr(value.threshold, 0),\n changedMoreThanRequested: value.changedMoreThanRequested === true,\n composited: value.composited === true,\n colorCheck: normalizeColorCheck(value.colorCheck),\n };\n};\n"],"names":[],"mappings":"AAmBA,MAAM,WAAW,CAAC,UAAqD,OAAO,UAAU,YAAY,UAAU;AAE9G,MAAM,WAAW,CAAI,OAAgB,aACnC,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;AAEhE,MAAM,sBAAsB,CAAC,UAA8C;AACzE,MAAI,CAAC,SAAS,KAAK,KAAK,OAAO,MAAM,WAAW,UAAW,QAAO;AAClE,SAAO;AAAA,IACL,WAAW,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY;AAAA,IACnE,aAAa,OAAO,MAAM,gBAAgB,WAAW,MAAM,cAAc;AAAA,IACzE,QAAQ,SAAS,MAAM,QAAQ,CAAC;AAAA,IAChC,QAAQ,MAAM;AAAA,EAAA;AAElB;AAGO,MAAM,+BAA+B,CAAC,UAAgD;AAC3F,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO;AAC7B,SAAO;AAAA,IACL,aAAa,SAAS,MAAM,aAAa,IAAI;AAAA,IAC7C,WAAW,SAAS,MAAM,WAAW,CAAC;AAAA,IACtC,0BAA0B,MAAM,6BAA6B;AAAA,IAC7D,YAAY,MAAM,eAAe;AAAA,IACjC,YAAY,oBAAoB,MAAM,UAAU;AAAA,EAAA;AAEpD;"}
|
|
@@ -1,11 +1,30 @@
|
|
|
1
|
+
import { IImageAiVerification } from './imageAiVerification';
|
|
2
|
+
export type { IImageAiColorCheck, IImageAiVerification } from './imageAiVerification';
|
|
3
|
+
/** Matches the backend limit for the adjustment instructions. */
|
|
4
|
+
export declare const IMAGE_AI_ADJUST_MAX_INSTRUCTIONS = 2000;
|
|
5
|
+
/**
|
|
6
|
+
* An adjuster may reject with an error carrying `displayMessage`: a short reason the host has
|
|
7
|
+
* already vetted as safe to show (for example why the model refused). Anything else stays generic.
|
|
8
|
+
*/
|
|
9
|
+
export interface IImageAiAdjustDisplayableError {
|
|
10
|
+
displayMessage: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const getImageAiAdjustDisplayMessage: (error: unknown) => string | null;
|
|
1
13
|
export interface IImageAiAdjustRequest {
|
|
2
|
-
/** The current image,
|
|
14
|
+
/** The current image at full resolution, only re-encoded when it exceeds the upload budget. */
|
|
3
15
|
baseImage: File;
|
|
4
16
|
instructions: string;
|
|
5
17
|
productId?: string;
|
|
18
|
+
/** PNG at the source's natural size: white = the only area that may change, black = keep. */
|
|
19
|
+
mask?: File;
|
|
6
20
|
}
|
|
7
21
|
export interface IImageAiAdjustResult {
|
|
8
22
|
imageUrl: string;
|
|
23
|
+
/** Present when the host logs generations; enables the approve/discard review. */
|
|
24
|
+
generationId?: string;
|
|
25
|
+
costUsd?: number;
|
|
26
|
+
/** Backend checks on the result; a warning is shown when it changed more than asked. */
|
|
27
|
+
verification?: IImageAiVerification | null;
|
|
9
28
|
}
|
|
10
29
|
/** The host decides which AI backend serves the adjustment; the UI only knows this contract. */
|
|
11
30
|
export type TImageAiAdjuster = (request: IImageAiAdjustRequest, signal: AbortSignal) => Promise<IImageAiAdjustResult>;
|
|
@@ -14,11 +33,15 @@ export interface IUseImageAiAdjustParams {
|
|
|
14
33
|
imageName?: string;
|
|
15
34
|
productId?: string;
|
|
16
35
|
adjustImage: TImageAiAdjuster;
|
|
36
|
+
/** Optional painted region; sent with the request only when present. */
|
|
37
|
+
mask?: File | null;
|
|
17
38
|
}
|
|
18
39
|
export interface IUseImageAiAdjust {
|
|
19
40
|
instructions: string;
|
|
20
41
|
setInstructions: (value: string) => void;
|
|
21
42
|
resultUrl: string | null;
|
|
43
|
+
resultGenerationId: string | null;
|
|
44
|
+
resultVerification: IImageAiVerification | null;
|
|
22
45
|
isGenerating: boolean;
|
|
23
46
|
canGenerate: boolean;
|
|
24
47
|
generate: () => Promise<void>;
|
|
@@ -26,4 +49,4 @@ export interface IUseImageAiAdjust {
|
|
|
26
49
|
discardResult: () => void;
|
|
27
50
|
buildResultFile: () => Promise<File>;
|
|
28
51
|
}
|
|
29
|
-
export declare const useImageAiAdjust: ({ imageUrl, imageName, productId, adjustImage, }: IUseImageAiAdjustParams) => IUseImageAiAdjust;
|
|
52
|
+
export declare const useImageAiAdjust: ({ imageUrl, imageName, productId, adjustImage, mask, }: IUseImageAiAdjustParams) => IUseImageAiAdjust;
|
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
import { useRef, useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { useTranslation } from "react-i18next";
|
|
3
|
-
import { fileFromUrl,
|
|
3
|
+
import { fileFromUrl, imageExtensionForType, fitImageToUploadBudget } from "../../../helpers/imageFile.js";
|
|
4
4
|
import { uiLogger } from "../../../helpers/uiLogger.js";
|
|
5
5
|
import { useNotification } from "../../../hooks/useNotification.js";
|
|
6
|
-
|
|
7
|
-
const
|
|
6
|
+
import { normalizeImageAiVerification } from "./imageAiVerification.js";
|
|
7
|
+
const IMAGE_AI_ADJUST_MAX_INSTRUCTIONS = 2e3;
|
|
8
8
|
const isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
|
|
9
|
-
const
|
|
9
|
+
const getImageAiAdjustDisplayMessage = (error) => {
|
|
10
|
+
if (typeof error !== "object" || error === null || !("displayMessage" in error)) return null;
|
|
11
|
+
const { displayMessage } = error;
|
|
12
|
+
return typeof displayMessage === "string" && displayMessage.trim() ? displayMessage.trim() : null;
|
|
13
|
+
};
|
|
14
|
+
const adjustedFileName = (imageName, type) => {
|
|
10
15
|
const base = (imageName ?? "image").replace(/\.[^.]+$/, "") || "image";
|
|
11
|
-
return `${base}-ia
|
|
16
|
+
return `${base}-ia.${imageExtensionForType(type)}`;
|
|
12
17
|
};
|
|
13
18
|
const useImageAiAdjust = ({
|
|
14
19
|
imageUrl,
|
|
15
20
|
imageName,
|
|
16
21
|
productId,
|
|
17
|
-
adjustImage
|
|
22
|
+
adjustImage,
|
|
23
|
+
mask
|
|
18
24
|
}) => {
|
|
19
25
|
const { t } = useTranslation("ui");
|
|
20
26
|
const { notify } = useNotification();
|
|
21
27
|
const abortRef = useRef(null);
|
|
22
28
|
const [instructions, setInstructions] = useState("");
|
|
23
29
|
const [resultUrl, setResultUrl] = useState(null);
|
|
30
|
+
const [resultGenerationId, setResultGenerationId] = useState(null);
|
|
31
|
+
const [resultVerification, setResultVerification] = useState(null);
|
|
24
32
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
25
33
|
useEffect(
|
|
26
34
|
() => () => {
|
|
@@ -32,46 +40,61 @@ const useImageAiAdjust = ({
|
|
|
32
40
|
const cancel = useCallback(() => {
|
|
33
41
|
abortRef.current?.abort();
|
|
34
42
|
}, []);
|
|
35
|
-
const discardResult = useCallback(() =>
|
|
43
|
+
const discardResult = useCallback(() => {
|
|
44
|
+
setResultUrl(null);
|
|
45
|
+
setResultGenerationId(null);
|
|
46
|
+
setResultVerification(null);
|
|
47
|
+
}, []);
|
|
36
48
|
const generate = useCallback(async () => {
|
|
37
49
|
const prompt = instructions.trim();
|
|
38
|
-
if (!prompt || isGenerating) return;
|
|
50
|
+
if (!prompt || prompt.length > IMAGE_AI_ADJUST_MAX_INSTRUCTIONS || isGenerating) return;
|
|
39
51
|
abortRef.current?.abort();
|
|
40
52
|
const controller = new AbortController();
|
|
41
53
|
abortRef.current = controller;
|
|
42
54
|
setIsGenerating(true);
|
|
43
55
|
try {
|
|
44
|
-
const
|
|
56
|
+
const fetched = await fileFromUrl(imageUrl, "base-image", controller.signal);
|
|
45
57
|
if (controller.signal.aborted) return;
|
|
46
|
-
const
|
|
58
|
+
const baseFile = new File([fetched], `base-image.${imageExtensionForType(fetched.type)}`, { type: fetched.type });
|
|
59
|
+
const baseImage = await fitImageToUploadBudget(baseFile);
|
|
47
60
|
if (controller.signal.aborted) return;
|
|
48
|
-
const
|
|
61
|
+
const request = { baseImage, instructions: prompt, productId };
|
|
62
|
+
if (mask) request.mask = mask;
|
|
63
|
+
const response = await adjustImage(request, controller.signal);
|
|
49
64
|
if (controller.signal.aborted) return;
|
|
50
65
|
setResultUrl(response.imageUrl);
|
|
66
|
+
setResultGenerationId(
|
|
67
|
+
typeof response.generationId === "string" && response.generationId ? response.generationId : null
|
|
68
|
+
);
|
|
69
|
+
setResultVerification(normalizeImageAiVerification(response.verification));
|
|
51
70
|
} catch (error) {
|
|
52
|
-
if (isAbortError(error)) return;
|
|
71
|
+
if (isAbortError(error) || controller.signal.aborted) return;
|
|
53
72
|
uiLogger.error("Failed to adjust the image with AI", error);
|
|
54
|
-
|
|
73
|
+
const reason = getImageAiAdjustDisplayMessage(error);
|
|
74
|
+
notify({
|
|
75
|
+
message: reason ? t("imageAiAdjust.errorRefused", { reason }) : t("imageAiAdjust.errorGenerate"),
|
|
76
|
+
type: "error"
|
|
77
|
+
});
|
|
55
78
|
} finally {
|
|
56
79
|
if (abortRef.current === controller) {
|
|
57
80
|
abortRef.current = null;
|
|
58
81
|
setIsGenerating(false);
|
|
59
82
|
}
|
|
60
83
|
}
|
|
61
|
-
}, [instructions, isGenerating, imageUrl, productId, adjustImage, notify, t]);
|
|
84
|
+
}, [instructions, isGenerating, imageUrl, productId, adjustImage, mask, notify, t]);
|
|
62
85
|
const buildResultFile = useCallback(async () => {
|
|
63
86
|
if (!resultUrl) throw new Error("No adjusted image to save");
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
quality: ADJUSTED_IMAGE_QUALITY
|
|
67
|
-
});
|
|
87
|
+
const fetched = await fileFromUrl(resultUrl, "adjusted-image");
|
|
88
|
+
return new File([fetched], adjustedFileName(imageName, fetched.type), { type: fetched.type });
|
|
68
89
|
}, [resultUrl, imageName]);
|
|
69
90
|
return {
|
|
70
91
|
instructions,
|
|
71
92
|
setInstructions,
|
|
72
93
|
resultUrl,
|
|
94
|
+
resultGenerationId,
|
|
95
|
+
resultVerification,
|
|
73
96
|
isGenerating,
|
|
74
|
-
canGenerate: Boolean(instructions.trim()) && !isGenerating,
|
|
97
|
+
canGenerate: Boolean(instructions.trim()) && instructions.trim().length <= IMAGE_AI_ADJUST_MAX_INSTRUCTIONS && !isGenerating,
|
|
75
98
|
generate,
|
|
76
99
|
cancel,
|
|
77
100
|
discardResult,
|
|
@@ -79,6 +102,8 @@ const useImageAiAdjust = ({
|
|
|
79
102
|
};
|
|
80
103
|
};
|
|
81
104
|
export {
|
|
105
|
+
IMAGE_AI_ADJUST_MAX_INSTRUCTIONS,
|
|
106
|
+
getImageAiAdjustDisplayMessage,
|
|
82
107
|
useImageAiAdjust
|
|
83
108
|
};
|
|
84
109
|
//# sourceMappingURL=useImageAiAdjust.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useImageAiAdjust.js","sources":["../../../../src/components/UI/ImageAiAdjust/useImageAiAdjust.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport { fileFromUrl,
|
|
1
|
+
{"version":3,"file":"useImageAiAdjust.js","sources":["../../../../src/components/UI/ImageAiAdjust/useImageAiAdjust.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport { fileFromUrl, fitImageToUploadBudget, imageExtensionForType } from '@/helpers/imageFile';\nimport { uiLogger } from '@/helpers/uiLogger';\nimport { useNotification } from '@/hooks/useNotification';\n\nimport { IImageAiVerification, normalizeImageAiVerification } from './imageAiVerification';\n\nexport type { IImageAiColorCheck, IImageAiVerification } from './imageAiVerification';\n\n/** Matches the backend limit for the adjustment instructions. */\nexport const IMAGE_AI_ADJUST_MAX_INSTRUCTIONS = 2000;\n\nconst isAbortError = (error: unknown): boolean => error instanceof DOMException && error.name === 'AbortError';\n\n/**\n * An adjuster may reject with an error carrying `displayMessage`: a short reason the host has\n * already vetted as safe to show (for example why the model refused). Anything else stays generic.\n */\nexport interface IImageAiAdjustDisplayableError {\n displayMessage: string;\n}\n\nexport const getImageAiAdjustDisplayMessage = (error: unknown): string | null => {\n if (typeof error !== 'object' || error === null || !('displayMessage' in error)) return null;\n const { displayMessage } = error as Partial<IImageAiAdjustDisplayableError>;\n return typeof displayMessage === 'string' && displayMessage.trim() ? displayMessage.trim() : null;\n};\n\nexport interface IImageAiAdjustRequest {\n /** The current image at full resolution, only re-encoded when it exceeds the upload budget. */\n baseImage: File;\n instructions: string;\n productId?: string;\n /** PNG at the source's natural size: white = the only area that may change, black = keep. */\n mask?: File;\n}\n\nexport interface IImageAiAdjustResult {\n imageUrl: string;\n /** Present when the host logs generations; enables the approve/discard review. */\n generationId?: string;\n costUsd?: number;\n /** Backend checks on the result; a warning is shown when it changed more than asked. */\n verification?: IImageAiVerification | null;\n}\n\n/** The host decides which AI backend serves the adjustment; the UI only knows this contract. */\nexport type TImageAiAdjuster = (request: IImageAiAdjustRequest, signal: AbortSignal) => Promise<IImageAiAdjustResult>;\n\nexport interface IUseImageAiAdjustParams {\n imageUrl: string;\n imageName?: string;\n productId?: string;\n adjustImage: TImageAiAdjuster;\n /** Optional painted region; sent with the request only when present. */\n mask?: File | null;\n}\n\nexport interface IUseImageAiAdjust {\n instructions: string;\n setInstructions: (value: string) => void;\n resultUrl: string | null;\n resultGenerationId: string | null;\n resultVerification: IImageAiVerification | null;\n isGenerating: boolean;\n canGenerate: boolean;\n generate: () => Promise<void>;\n cancel: () => void;\n discardResult: () => void;\n buildResultFile: () => Promise<File>;\n}\n\nconst adjustedFileName = (imageName: string | undefined, type: string): string => {\n const base = (imageName ?? 'image').replace(/\\.[^.]+$/, '') || 'image';\n return `${base}-ia.${imageExtensionForType(type)}`;\n};\n\nexport const useImageAiAdjust = ({\n imageUrl,\n imageName,\n productId,\n adjustImage,\n mask,\n}: IUseImageAiAdjustParams): IUseImageAiAdjust => {\n const { t } = useTranslation('ui');\n const { notify } = useNotification();\n\n const abortRef = useRef<AbortController | null>(null);\n const [instructions, setInstructions] = useState('');\n const [resultUrl, setResultUrl] = useState<string | null>(null);\n const [resultGenerationId, setResultGenerationId] = useState<string | null>(null);\n const [resultVerification, setResultVerification] = useState<IImageAiVerification | null>(null);\n const [isGenerating, setIsGenerating] = useState(false);\n\n // Closing the dialog mid-generation would leave an orphan request holding the connection.\n useEffect(\n () => () => {\n abortRef.current?.abort();\n abortRef.current = null;\n },\n [],\n );\n\n const cancel = useCallback(() => {\n abortRef.current?.abort();\n }, []);\n\n const discardResult = useCallback(() => {\n setResultUrl(null);\n setResultGenerationId(null);\n setResultVerification(null);\n }, []);\n\n const generate = useCallback(async () => {\n const prompt = instructions.trim();\n if (!prompt || prompt.length > IMAGE_AI_ADJUST_MAX_INSTRUCTIONS || isGenerating) return;\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n setIsGenerating(true);\n\n try {\n const fetched = await fileFromUrl(imageUrl, 'base-image', controller.signal);\n if (controller.signal.aborted) return;\n\n const baseFile = new File([fetched], `base-image.${imageExtensionForType(fetched.type)}`, { type: fetched.type });\n const baseImage = await fitImageToUploadBudget(baseFile);\n if (controller.signal.aborted) return;\n\n const request: IImageAiAdjustRequest = { baseImage, instructions: prompt, productId };\n if (mask) request.mask = mask;\n const response = await adjustImage(request, controller.signal);\n if (controller.signal.aborted) return;\n\n setResultUrl(response.imageUrl);\n setResultGenerationId(\n typeof response.generationId === 'string' && response.generationId ? response.generationId : null,\n );\n setResultVerification(normalizeImageAiVerification(response.verification));\n } catch (error) {\n // Hosts may reject a cancelled request with their own error type (axios CanceledError).\n if (isAbortError(error) || controller.signal.aborted) return;\n uiLogger.error('Failed to adjust the image with AI', error);\n const reason = getImageAiAdjustDisplayMessage(error);\n notify({\n message: reason ? t('imageAiAdjust.errorRefused', { reason }) : t('imageAiAdjust.errorGenerate'),\n type: 'error',\n });\n } finally {\n if (abortRef.current === controller) {\n abortRef.current = null;\n setIsGenerating(false);\n }\n }\n }, [instructions, isGenerating, imageUrl, productId, adjustImage, mask, notify, t]);\n\n const buildResultFile = useCallback(async () => {\n if (!resultUrl) throw new Error('No adjusted image to save');\n\n // Kept byte for byte: re-encoding would throw away the quality the adjustment preserved.\n const fetched = await fileFromUrl(resultUrl, 'adjusted-image');\n return new File([fetched], adjustedFileName(imageName, fetched.type), { type: fetched.type });\n }, [resultUrl, imageName]);\n\n return {\n instructions,\n setInstructions,\n resultUrl,\n resultGenerationId,\n resultVerification,\n isGenerating,\n canGenerate:\n Boolean(instructions.trim()) && instructions.trim().length <= IMAGE_AI_ADJUST_MAX_INSTRUCTIONS && !isGenerating,\n generate,\n cancel,\n discardResult,\n buildResultFile,\n };\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type { TMaskBrushMode } from './maskStrokes';
|
|
2
|
+
/** Brush diameter in on-screen pixels; converted to natural pixels per stroke. */
|
|
3
|
+
export declare const IMAGE_MASK_BRUSH_MIN = 8;
|
|
4
|
+
export declare const IMAGE_MASK_BRUSH_MAX = 96;
|
|
5
|
+
export interface IImageMaskPainterProps {
|
|
6
|
+
imageUrl: string;
|
|
7
|
+
/**
|
|
8
|
+
* Called after every stroke, undo and clear with a PNG at the image's natural resolution
|
|
9
|
+
* (white = painted, black = untouched), or null when nothing is painted.
|
|
10
|
+
*/
|
|
11
|
+
onMaskChange: (mask: File | null) => void;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
maskFileName?: string;
|
|
14
|
+
/** Sizes the image; defaults to fitting a dialog preview. */
|
|
15
|
+
imageClassName?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
declare const ImageMaskPainter: React.FC<IImageMaskPainterProps>;
|
|
19
|
+
export default ImageMaskPainter;
|