From 19c7ab6fed22f56e04772329cabe1ebf3a37393f Mon Sep 17 00:00:00 2001 From: sragss Date: Tue, 14 Oct 2025 11:28:23 -0400 Subject: [PATCH] add img compression --- pnpm-lock.yaml | 265 ++++++++++++++++-- templates/next-image/package.json | 1 + .../src/components/image-generator.tsx | 13 +- templates/next-image/src/lib/constants.ts | 9 + .../next-image/src/lib/image-compression.ts | 41 +++ 5 files changed, 300 insertions(+), 29 deletions(-) create mode 100644 templates/next-image/src/lib/image-compression.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 013382483..98c9642e6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1378,6 +1378,103 @@ importers: specifier: ^3.2.3 version: 3.2.3(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/ui@3.2.3)(jiti@2.5.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(lightningcss@1.30.1)(msw@2.11.2(@types/node@24.3.1)(typescript@5.9.2))(terser@5.42.0)(tsx@4.20.5)(yaml@2.8.0) + templates/next-image: + dependencies: + '@merit-systems/echo-next-sdk': + specifier: 0.0.24 + version: 0.0.24(@ai-sdk/react@2.0.47(react@19.1.0)(zod@4.1.11))(next@15.4.7(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(openai@5.20.3(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@4.1.11) + '@merit-systems/echo-react-sdk': + specifier: 1.0.33 + version: 1.0.33(@ai-sdk/react@2.0.47(react@19.1.0)(zod@4.1.11))(ai@5.0.47(zod@4.1.11))(openai@5.20.3(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@4.1.11) + '@radix-ui/react-avatar': + specifier: ^1.1.10 + version: 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + ai: + specifier: 5.0.47 + version: 5.0.47(zod@4.1.11) + autonumeric: + specifier: ^4.10.9 + version: 4.10.9 + browser-image-compression: + specifier: ^2.0.2 + version: 2.0.2 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.263.1 + version: 0.263.1(react@19.1.0) + nanoid: + specifier: ^5.1.5 + version: 5.1.5 + next: + specifier: 15.4.7 + version: 15.4.7(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + openai: + specifier: ^5.20.3 + version: 5.20.3(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11) + react: + specifier: 19.1.0 + version: 19.1.0 + react-dom: + specifier: 19.1.0 + version: 19.1.0(react@19.1.0) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + devDependencies: + '@eslint/eslintrc': + specifier: ^3 + version: 3.3.1 + '@tailwindcss/postcss': + specifier: ^4 + version: 4.1.11 + '@types/node': + specifier: ^20 + version: 20.19.16 + '@types/react': + specifier: 19.1.10 + version: 19.1.10 + '@types/react-dom': + specifier: 19.1.7 + version: 19.1.7(@types/react@19.1.10) + eslint: + specifier: ^9 + version: 9.35.0(jiti@2.5.1) + eslint-config-next: + specifier: 15.4.7 + version: 15.4.7(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + tailwindcss: + specifier: ^4 + version: 4.1.13 + tw-animate-css: + specifier: ^1.3.8 + version: 1.3.8 + typescript: + specifier: ^5 + version: 5.9.2 + packages: '@adobe/css-tools@4.4.3': @@ -1778,9 +1875,6 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 - '@emnapi/core@1.4.3': - resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} - '@emnapi/core@1.5.0': resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} @@ -1790,9 +1884,6 @@ packages: '@emnapi/runtime@1.5.0': resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} - '@emnapi/wasi-threads@1.0.2': - resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} - '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} @@ -6196,6 +6287,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browser-image-compression@2.0.2: + resolution: {integrity: sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==} + browserslist@4.25.0: resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -8812,6 +8906,11 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lucide-react@0.263.1: + resolution: {integrity: sha512-keqxAx97PlaEN89PXZ6ki1N8nRjGWtDa4021GFYLNj0RgruM5odbpl8GHTExj0hhPq3sF6Up0gnxt6TSHu+ovw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + lucide-react@0.487.0: resolution: {integrity: sha512-aKqhOQ+YmFnwq8dWgGjOuLc8V1R9/c/yOd+zDY4+ohsR2Jo05lSGc3WsstYPIzcTpeosN7LoCkLReUUITvaIvw==} peerDependencies: @@ -10894,6 +10993,9 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwind-merge@2.6.0: + resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} + tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} @@ -11443,6 +11545,9 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + uzip@0.20201231.0: + resolution: {integrity: sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng==} + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -12707,12 +12812,6 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 - '@emnapi/core@1.4.3': - dependencies: - '@emnapi/wasi-threads': 1.0.2 - tslib: 2.8.1 - optional: true - '@emnapi/core@1.5.0': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -12729,11 +12828,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.0.2': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/wasi-threads@1.1.0': dependencies: tslib: 2.8.1 @@ -13357,6 +13451,21 @@ snapshots: - openai - zod + '@merit-systems/echo-next-sdk@0.0.24(@ai-sdk/react@2.0.47(react@19.1.0)(zod@4.1.11))(next@15.4.7(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(openai@5.20.3(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@4.1.11)': + dependencies: + '@merit-systems/echo-react-sdk': 1.0.33(@ai-sdk/react@2.0.47(react@19.1.0)(zod@4.1.11))(ai@5.0.47(zod@4.1.11))(openai@5.20.3(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@4.1.11) + '@merit-systems/echo-typescript-sdk': 1.0.17(zod@4.1.11) + ai: 5.0.47(zod@4.1.11) + jwt-decode: 4.0.0 + next: 15.4.7(@opentelemetry/api@1.9.0)(@playwright/test@1.53.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + swr: 2.3.6(react@19.1.0) + transitivePeerDependencies: + - '@ai-sdk/react' + - openai + - zod + '@merit-systems/echo-react-sdk@1.0.33(@ai-sdk/react@2.0.17(react@19.1.0)(zod@4.1.11))(ai@5.0.19(zod@4.1.11))(openai@5.20.3(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@4.1.11)': dependencies: '@ai-sdk/react': 2.0.17(react@19.1.0)(zod@4.1.11) @@ -13389,6 +13498,22 @@ snapshots: transitivePeerDependencies: - zod + '@merit-systems/echo-react-sdk@1.0.33(@ai-sdk/react@2.0.47(react@19.1.0)(zod@4.1.11))(ai@5.0.47(zod@4.1.11))(openai@5.20.3(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@4.1.11)': + dependencies: + '@ai-sdk/react': 2.0.47(react@19.1.0)(zod@4.1.11) + '@merit-systems/echo-typescript-sdk': 1.0.17(zod@4.1.11) + ai: 5.0.47(zod@4.1.11) + dompurify: 3.2.6 + oidc-client-ts: 3.3.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-oidc-context: 3.3.0(oidc-client-ts@3.3.0)(react@19.1.0) + swr: 2.3.6(react@19.1.0) + optionalDependencies: + openai: 5.20.3(ws@8.18.2(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@4.1.11) + transitivePeerDependencies: + - zod + '@merit-systems/echo-typescript-sdk@1.0.17(zod@4.1.11)': dependencies: '@ai-sdk/anthropic': 2.0.17(zod@4.1.11) @@ -13711,8 +13836,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.11': dependencies: - '@emnapi/core': 1.4.3 - '@emnapi/runtime': 1.4.5 + '@emnapi/core': 1.5.0 + '@emnapi/runtime': 1.5.0 '@tybys/wasm-util': 0.9.0 optional: true @@ -15217,6 +15342,21 @@ snapshots: '@types/react': 19.1.10 '@types/react-dom': 19.1.7(@types/react@19.1.10) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.10)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.10 + '@types/react-dom': 19.1.7(@types/react@19.1.10) + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -15315,6 +15455,32 @@ snapshots: '@types/react': 19.1.10 '@types/react-dom': 19.1.7(@types/react@19.1.10) + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.10)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.10)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.10 + '@types/react-dom': 19.1.7(@types/react@19.1.10) + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -15503,6 +15669,23 @@ snapshots: '@types/react': 19.1.10 '@types/react-dom': 19.1.7(@types/react@19.1.10) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.10)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.10)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.10 + '@types/react-dom': 19.1.7(@types/react@19.1.10) + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -20841,6 +21024,10 @@ snapshots: dependencies: fill-range: 7.1.1 + browser-image-compression@2.0.2: + dependencies: + uzip: 0.20201231.0 + browserslist@4.25.0: dependencies: caniuse-lite: 1.0.30001721 @@ -21911,7 +22098,7 @@ snapshots: '@typescript-eslint/parser': 8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) @@ -21970,14 +22157,29 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.1 + eslint: 9.35.0(jiti@2.5.1) + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.9.0 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -21992,7 +22194,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)))(eslint@9.35.0(jiti@2.5.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.34.1(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -23837,6 +24039,10 @@ snapshots: dependencies: yallist: 4.0.0 + lucide-react@0.263.1(react@19.1.0): + dependencies: + react: 19.1.0 + lucide-react@0.487.0(react@19.1.0): dependencies: react: 19.1.0 @@ -24619,11 +24825,11 @@ snapshots: dependencies: '@next/env': 15.4.7 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001721 + caniuse-lite: 1.0.30001743 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(@babel/core@7.28.4)(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: '@next/swc-darwin-arm64': 15.4.7 '@next/swc-darwin-x64': 15.4.7 @@ -26719,6 +26925,11 @@ snapshots: client-only: 0.0.1 react: 18.3.1 + styled-jsx@5.1.6(react@19.1.0): + dependencies: + client-only: 0.0.1 + react: 19.1.0 + styled-jsx@5.1.6(react@19.1.1): dependencies: client-only: 0.0.1 @@ -26796,6 +27007,8 @@ snapshots: symbol-tree@3.2.4: {} + tailwind-merge@2.6.0: {} + tailwind-merge@3.3.1: {} tailwindcss@3.4.17(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.8.3)): @@ -27477,6 +27690,8 @@ snapshots: uuid@9.0.1: {} + uzip@0.20201231.0: {} + v8-compile-cache-lib@3.0.1: {} valtio@1.13.2: diff --git a/templates/next-image/package.json b/templates/next-image/package.json index 6d0fcbf62..f45b2cff7 100644 --- a/templates/next-image/package.json +++ b/templates/next-image/package.json @@ -20,6 +20,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "ai": "5.0.47", "autonumeric": "^4.10.9", + "browser-image-compression": "^2.0.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.263.1", diff --git a/templates/next-image/src/components/image-generator.tsx b/templates/next-image/src/components/image-generator.tsx index e585d3cc5..c6ed6d039 100644 --- a/templates/next-image/src/components/image-generator.tsx +++ b/templates/next-image/src/components/image-generator.tsx @@ -26,6 +26,7 @@ import { X } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { fileToDataUrl } from '@/lib/image-utils'; +import { compressImage } from '@/lib/image-compression'; import type { EditImageRequest, GeneratedImage, @@ -219,12 +220,16 @@ export default function ImageGenerator() { try { const imageUrls = await Promise.all( imageFiles.map(async imageFile => { - // Convert blob URL to data URL for API + // Convert blob URL to File const response = await fetch(imageFile.url); const blob = await response.blob(); - return await fileToDataUrl( - new File([blob], 'image', { type: imageFile.mediaType }) - ); + const file = new File([blob], 'image', { type: imageFile.mediaType }); + + // Compress before converting to data URL + const compressed = await compressImage(file); + + // Convert compressed file to data URL for API + return await fileToDataUrl(compressed); }) ); diff --git a/templates/next-image/src/lib/constants.ts b/templates/next-image/src/lib/constants.ts index 561a2f71f..e04945047 100644 --- a/templates/next-image/src/lib/constants.ts +++ b/templates/next-image/src/lib/constants.ts @@ -2,6 +2,15 @@ * Application constants and error messages */ +/** + * Maximum size for image compression before upload (in MB) + * Adjust this value to tune quality vs. size tradeoff + * - Lower values (0.5): Smaller files, faster uploads, lower quality + * - Higher values (1.5): Better quality, slower uploads, may hit API limits + * - Recommended: 0.75 (good balance for AI image processing) + */ +export const MAX_IMAGE_SIZE_MB = 0.75; + export const ERROR_MESSAGES = { AUTH_FAILED: 'Authentication failed. No token available.', GENERATION_FAILED: 'Image generation failed. Please try again later.', diff --git a/templates/next-image/src/lib/image-compression.ts b/templates/next-image/src/lib/image-compression.ts new file mode 100644 index 000000000..17ce90304 --- /dev/null +++ b/templates/next-image/src/lib/image-compression.ts @@ -0,0 +1,41 @@ +/** + * Image compression utilities + * Compresses images before upload to reduce payload size and improve performance + */ + +import imageCompression from 'browser-image-compression'; +import { MAX_IMAGE_SIZE_MB } from './constants'; + +/** + * Compresses an image file to the configured maximum size + * Uses browser-image-compression library with web workers for non-blocking compression + * + * @param file - The image file to compress + * @returns Compressed image file + */ +export async function compressImage(file: File): Promise { + try { + const compressed = await imageCompression(file, { + maxSizeMB: MAX_IMAGE_SIZE_MB, + useWebWorker: true, + fileType: file.type, + }); + + return compressed; + } catch (error) { + console.error('Image compression failed:', error); + // If compression fails, return original file + // Better to try with larger file than fail completely + return file; + } +} + +/** + * Compresses multiple image files in parallel + * + * @param files - Array of image files to compress + * @returns Array of compressed image files + */ +export async function compressImages(files: File[]): Promise { + return Promise.all(files.map(compressImage)); +}