@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.
Files changed (61) hide show
  1. package/dist/components/Form/ControlledImage/index.d.ts +3 -0
  2. package/dist/components/Form/ControlledImage/index.js +2 -1
  3. package/dist/components/Form/ControlledImage/index.js.map +1 -1
  4. package/dist/components/UI/DataTable/DataTable.js +15 -4
  5. package/dist/components/UI/DataTable/DataTable.js.map +1 -1
  6. package/dist/components/UI/DataTable/DataTableBody.d.ts +5 -1
  7. package/dist/components/UI/DataTable/DataTableBody.js +15 -2
  8. package/dist/components/UI/DataTable/DataTableBody.js.map +1 -1
  9. package/dist/components/UI/DataTable/DataTableBodyCell.js +11 -0
  10. package/dist/components/UI/DataTable/DataTableBodyCell.js.map +1 -1
  11. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js +7 -10
  12. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js.map +1 -1
  13. package/dist/components/UI/DataTable/grid/GridProvider.d.ts +3 -0
  14. package/dist/components/UI/DataTable/grid/GridProvider.js +25 -3
  15. package/dist/components/UI/DataTable/grid/GridProvider.js.map +1 -1
  16. package/dist/components/UI/DataTable/grid/gridStore.d.ts +10 -2
  17. package/dist/components/UI/DataTable/grid/gridStore.js +30 -3
  18. package/dist/components/UI/DataTable/grid/gridStore.js.map +1 -1
  19. package/dist/components/UI/DataTable/grid/gridTypes.d.ts +17 -5
  20. package/dist/components/UI/DataTable/grid/gridTypes.js.map +1 -1
  21. package/dist/components/UI/DataTable/grid/useGridKeyboard.d.ts +4 -2
  22. package/dist/components/UI/DataTable/grid/useGridKeyboard.js +25 -11
  23. package/dist/components/UI/DataTable/grid/useGridKeyboard.js.map +1 -1
  24. package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.d.ts +6 -0
  25. package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.js +164 -74
  26. package/dist/components/UI/ImageAiAdjust/ImageAiAdjustDialog.js.map +1 -1
  27. package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.d.ts +8 -0
  28. package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.js +67 -0
  29. package/dist/components/UI/ImageAiAdjust/ImageAiDiscardReasonDialog.js.map +1 -0
  30. package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.d.ts +8 -0
  31. package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.js +39 -0
  32. package/dist/components/UI/ImageAiAdjust/ImageAiVerificationNotice.js.map +1 -0
  33. package/dist/components/UI/ImageAiAdjust/imageAiReview.d.ts +22 -0
  34. package/dist/components/UI/ImageAiAdjust/imageAiReview.js +37 -0
  35. package/dist/components/UI/ImageAiAdjust/imageAiReview.js.map +1 -0
  36. package/dist/components/UI/ImageAiAdjust/imageAiVerification.d.ts +19 -0
  37. package/dist/components/UI/ImageAiAdjust/imageAiVerification.js +25 -0
  38. package/dist/components/UI/ImageAiAdjust/imageAiVerification.js.map +1 -0
  39. package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.d.ts +25 -2
  40. package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.js +44 -19
  41. package/dist/components/UI/ImageAiAdjust/useImageAiAdjust.js.map +1 -1
  42. package/dist/components/UI/ImageMaskPainter/index.d.ts +19 -0
  43. package/dist/components/UI/ImageMaskPainter/index.js +267 -0
  44. package/dist/components/UI/ImageMaskPainter/index.js.map +1 -0
  45. package/dist/components/UI/ImageMaskPainter/maskStrokes.d.ts +19 -0
  46. package/dist/components/UI/ImageMaskPainter/maskStrokes.js +71 -0
  47. package/dist/components/UI/ImageMaskPainter/maskStrokes.js.map +1 -0
  48. package/dist/helpers/imageFile.d.ts +16 -0
  49. package/dist/helpers/imageFile.js +59 -10
  50. package/dist/helpers/imageFile.js.map +1 -1
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +12 -10
  53. package/dist/index.js.map +1 -1
  54. package/dist/locales/en/ui.json.d.ts +36 -1
  55. package/dist/locales/en/ui.json.js +7 -1
  56. package/dist/locales/en/ui.json.js.map +1 -1
  57. package/dist/locales/index.d.ts +70 -0
  58. package/dist/locales/pt/ui.json.d.ts +36 -1
  59. package/dist/locales/pt/ui.json.js +7 -1
  60. package/dist/locales/pt/ui.json.js.map +1 -1
  61. 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, already resized for transport. */
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, resizeImageFile } from "../../../helpers/imageFile.js";
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
- const ADJUSTED_IMAGE_MAX_DIMENSION = 1600;
7
- const ADJUSTED_IMAGE_QUALITY = 0.9;
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 adjustedFileName = (imageName) => {
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.jpg`;
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(() => setResultUrl(null), []);
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 baseFile = await fileFromUrl(imageUrl, "base-image.jpg", controller.signal);
56
+ const fetched = await fileFromUrl(imageUrl, "base-image", controller.signal);
45
57
  if (controller.signal.aborted) return;
46
- const baseImage = await resizeImageFile(baseFile);
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 response = await adjustImage({ baseImage, instructions: prompt, productId }, controller.signal);
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
- notify({ message: t("imageAiAdjust.errorGenerate"), type: "error" });
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
- return resizeImageFile(await fileFromUrl(resultUrl, adjustedFileName(imageName)), {
65
- maxDimension: ADJUSTED_IMAGE_MAX_DIMENSION,
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, resizeImageFile } from '@/helpers/imageFile';\nimport { uiLogger } from '@/helpers/uiLogger';\nimport { useNotification } from '@/hooks/useNotification';\n\nconst ADJUSTED_IMAGE_MAX_DIMENSION = 1600;\nconst ADJUSTED_IMAGE_QUALITY = 0.9;\n\nconst isAbortError = (error: unknown): boolean => error instanceof DOMException && error.name === 'AbortError';\n\nexport interface IImageAiAdjustRequest {\n /** The current image, already resized for transport. */\n baseImage: File;\n instructions: string;\n productId?: string;\n}\n\nexport interface IImageAiAdjustResult {\n imageUrl: string;\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}\n\nexport interface IUseImageAiAdjust {\n instructions: string;\n setInstructions: (value: string) => void;\n resultUrl: string | 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): string => {\n const base = (imageName ?? 'image').replace(/\\.[^.]+$/, '') || 'image';\n return `${base}-ia.jpg`;\n};\n\nexport const useImageAiAdjust = ({\n imageUrl,\n imageName,\n productId,\n adjustImage,\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 [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(() => setResultUrl(null), []);\n\n const generate = useCallback(async () => {\n const prompt = instructions.trim();\n if (!prompt || isGenerating) return;\n\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n setIsGenerating(true);\n\n try {\n const baseFile = await fileFromUrl(imageUrl, 'base-image.jpg', controller.signal);\n if (controller.signal.aborted) return;\n\n const baseImage = await resizeImageFile(baseFile);\n if (controller.signal.aborted) return;\n\n const response = await adjustImage({ baseImage, instructions: prompt, productId }, controller.signal);\n if (controller.signal.aborted) return;\n\n setResultUrl(response.imageUrl);\n } catch (error) {\n if (isAbortError(error)) return;\n uiLogger.error('Failed to adjust the image with AI', error);\n notify({ message: t('imageAiAdjust.errorGenerate'), type: 'error' });\n } finally {\n if (abortRef.current === controller) {\n abortRef.current = null;\n setIsGenerating(false);\n }\n }\n }, [instructions, isGenerating, imageUrl, productId, adjustImage, notify, t]);\n\n const buildResultFile = useCallback(async () => {\n if (!resultUrl) throw new Error('No adjusted image to save');\n\n return resizeImageFile(await fileFromUrl(resultUrl, adjustedFileName(imageName)), {\n maxDimension: ADJUSTED_IMAGE_MAX_DIMENSION,\n quality: ADJUSTED_IMAGE_QUALITY,\n });\n }, [resultUrl, imageName]);\n\n return {\n instructions,\n setInstructions,\n resultUrl,\n isGenerating,\n canGenerate: Boolean(instructions.trim()) && !isGenerating,\n generate,\n cancel,\n discardResult,\n buildResultFile,\n };\n};\n"],"names":[],"mappings":";;;;;AAOA,MAAM,+BAA+B;AACrC,MAAM,yBAAyB;AAE/B,MAAM,eAAe,CAAC,UAA4B,iBAAiB,gBAAgB,MAAM,SAAS;AAmClG,MAAM,mBAAmB,CAAC,cAA0C;AAClE,QAAM,QAAQ,aAAa,SAAS,QAAQ,YAAY,EAAE,KAAK;AAC/D,SAAO,GAAG,IAAI;AAChB;AAEO,MAAM,mBAAmB,CAAC;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAkD;AAChD,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,EAAE,OAAA,IAAW,gBAAA;AAEnB,QAAM,WAAW,OAA+B,IAAI;AACpD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,EAAE;AACnD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAwB,IAAI;AAC9D,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AAGtD;AAAA,IACE,MAAM,MAAM;AACV,eAAS,SAAS,MAAA;AAClB,eAAS,UAAU;AAAA,IACrB;AAAA,IACA,CAAA;AAAA,EAAC;AAGH,QAAM,SAAS,YAAY,MAAM;AAC/B,aAAS,SAAS,MAAA;AAAA,EACpB,GAAG,CAAA,CAAE;AAEL,QAAM,gBAAgB,YAAY,MAAM,aAAa,IAAI,GAAG,CAAA,CAAE;AAE9D,QAAM,WAAW,YAAY,YAAY;AACvC,UAAM,SAAS,aAAa,KAAA;AAC5B,QAAI,CAAC,UAAU,aAAc;AAE7B,aAAS,SAAS,MAAA;AAClB,UAAM,aAAa,IAAI,gBAAA;AACvB,aAAS,UAAU;AACnB,oBAAgB,IAAI;AAEpB,QAAI;AACF,YAAM,WAAW,MAAM,YAAY,UAAU,kBAAkB,WAAW,MAAM;AAChF,UAAI,WAAW,OAAO,QAAS;AAE/B,YAAM,YAAY,MAAM,gBAAgB,QAAQ;AAChD,UAAI,WAAW,OAAO,QAAS;AAE/B,YAAM,WAAW,MAAM,YAAY,EAAE,WAAW,cAAc,QAAQ,UAAA,GAAa,WAAW,MAAM;AACpG,UAAI,WAAW,OAAO,QAAS;AAE/B,mBAAa,SAAS,QAAQ;AAAA,IAChC,SAAS,OAAO;AACd,UAAI,aAAa,KAAK,EAAG;AACzB,eAAS,MAAM,sCAAsC,KAAK;AAC1D,aAAO,EAAE,SAAS,EAAE,6BAA6B,GAAG,MAAM,SAAS;AAAA,IACrE,UAAA;AACE,UAAI,SAAS,YAAY,YAAY;AACnC,iBAAS,UAAU;AACnB,wBAAgB,KAAK;AAAA,MACvB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,cAAc,cAAc,UAAU,WAAW,aAAa,QAAQ,CAAC,CAAC;AAE5E,QAAM,kBAAkB,YAAY,YAAY;AAC9C,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,2BAA2B;AAE3D,WAAO,gBAAgB,MAAM,YAAY,WAAW,iBAAiB,SAAS,CAAC,GAAG;AAAA,MAChF,cAAc;AAAA,MACd,SAAS;AAAA,IAAA,CACV;AAAA,EACH,GAAG,CAAC,WAAW,SAAS,CAAC;AAEzB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,QAAQ,aAAa,KAAA,CAAM,KAAK,CAAC;AAAA,IAC9C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
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;