Introduce @uppy/image-generator (#6056)

Closes #5378 

- Introduce `@uppy/image-generator`, a new plugin to generate images
based on a prompt via Transloadit
- until we have "golden templates" the idea is to just send
[steps](https://transloadit.com/docs/topics/templates/#overruling-templates-at-runtime)
- because we must send steps and since we must use signature
authentication for security, which is signed based on the params we
send, we can't reuse the `assemblyOptions` the consumers is already
passing to `@uppy/transloadit` (if they use that uploaders, not needed).
- Remove `SearchInput` (this component was trying to be too many things,
all with conditional boolean props, which is bad practise) in favor of
`useSearchForm` and reuse this hook in two new components `SearchView`
and `FilterInput`
- Reuse all the styles from `SearchProviderView`. This deviates from the
design in #5378. It felt too inconsistent to me to do another UI here
again. For the initial version, I think it's best to stay consistent and
then redesign with search providers taken into account too.
- Because the service is so slow, I went a bit further with the loading
state to show funny messages that rotate while loading mostly because
users will start thinking it is broken after 5 seconds while it fact we
are still loading. But open to ideas here.

This unfortunately means the integration for the consumer is not as lean
and pretty as you would hope. On the upside, it does give them complete
freedom.

```ts
.use(ImageGenerator, {
  assemblyOptions: async (prompt) => {
    const res = await fetch(`/assembly-options?prompt=${encodeURIComponent(prompt)}`)
    return res.json()
  }
})
```

on the consumer's server:

```ts
import crypto from 'node:crypto'

const utcDateString = (ms) => {
  return new Date(ms)
    .toISOString()
    .replace(/-/g, '/')
    .replace(/T/, ' ')
    .replace(/\.\d+Z$/, '+00:00')
}

// expire 1 hour from now (this must be milliseconds)
const expires = utcDateString(Date.now() + 1 * 60 * 60 * 1000)
const authKey = 'YOUR_TRANSLOADIT_KEY'
const authSecret = 'YOUR_TRANSLOADIT_SECRET'

const params = JSON.stringify({
  auth: {
    key: authKey,
    expires,
  },
  // can not contain any more steps, the only step must be /image/generate
  steps: {
    generated_image: { // can be named different
      robot: '/image/generate',
      result: true, // mandatory
      aspect_ratio: '2:3', // up to them
      model: 'flux-1.1-pro-ultra', // up to them
      prompt, // mandatory
      num_outputs: 2, // up to them
    },
  },
})
const signatureBytes = crypto.createHmac('sha384', authSecret).update(Buffer.from(params, 'utf-8'))
// The final signature needs the hash name in front, so
// the hashing algorithm can be updated in a backwards-compatible
// way when old algorithms become insecure.
const signature = `sha384:${signatureBytes.digest('hex')}`

// respond with { params, signature } JSON to the client
```


https://github.com/user-attachments/assets/9217e457-b38b-48ac-81f0-37a417309e98



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds AI image generation plugin using Transloadit, exports low-level
Transloadit APIs, and replaces SearchInput with new
FilterInput/SearchView + useSearchForm across provider views.
> 
> - **New plugin: `@uppy/image-generator`**
> - UI plugin to generate images from a prompt via Transloadit
(`src/index.tsx`, styles, locale, build configs).
> - Integrated into dev Dashboard and included in `uppy` bundle and
global styles.
> - **Provider Views refactor**
> - Remove `SearchInput`; introduce `useSearchForm`, `SearchView`, and
`FilterInput` components.
> - Update `ProviderView`, `SearchProviderView`, and `Webdav` to use new
components; export them from `@uppy/provider-views`.
> - **Transloadit updates**
> - Export `Assembly`, `AssemblyError`, and `Client` from
`@uppy/transloadit`.
>   - Minor internal change: normalize `assemblyOptions.fields`.
> - **Locales**
> - Add strings for image generation and minor additions (e.g.,
`chooseFiles`).
>   - Ensure locales build depends on `@uppy/image-generator`.
> - **Build config**
> - Turborepo: add `uppy#build:css` and hook `image-generator` into
locales build.
> - **Changesets**
> - `@uppy/image-generator` major; `@uppy/transloadit` minor;
`@uppy/locales` and `uppy` minor; `@uppy/provider-views` and
`@uppy/webdav` patch.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
4b1b729069. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Prakash <qxprakash@gmail.com>
This commit is contained in:
Merlijn Vos 2025-12-03 11:59:52 +01:00 committed by GitHub
parent 93ef1ba0e7
commit 5684efa64e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 895 additions and 205 deletions

View file

@ -4975,18 +4975,7 @@ __metadata:
languageName: node
linkType: hard
"@eslint-community/eslint-utils@npm:^4.2.0":
version: 4.7.0
resolution: "@eslint-community/eslint-utils@npm:4.7.0"
dependencies:
eslint-visitor-keys: "npm:^3.4.3"
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
checksum: 10/43ed5d391526d9f5bbe452aef336389a473026fca92057cf97c576db11401ce9bcf8ef0bf72625bbaf6207ed8ba6bf0dcf4d7e809c24f08faa68a28533c491a7
languageName: node
linkType: hard
"@eslint-community/eslint-utils@npm:^4.4.0":
"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0":
version: 4.4.0
resolution: "@eslint-community/eslint-utils@npm:4.4.0"
dependencies:
@ -10983,6 +10972,24 @@ __metadata:
languageName: unknown
linkType: soft
"@uppy/image-generator@workspace:*, @uppy/image-generator@workspace:packages/@uppy/image-generator":
version: 0.0.0-use.local
resolution: "@uppy/image-generator@workspace:packages/@uppy/image-generator"
dependencies:
"@uppy/core": "workspace:^"
"@uppy/provider-views": "workspace:^"
"@uppy/transloadit": "workspace:^"
"@uppy/utils": "workspace:^"
cssnano: "npm:^7.0.7"
postcss: "npm:^8.5.6"
postcss-cli: "npm:^11.0.1"
preact: "npm:^10.5.13"
sass: "npm:^1.89.2"
peerDependencies:
"@uppy/core": "workspace:^"
languageName: unknown
linkType: soft
"@uppy/instagram@workspace:*, @uppy/instagram@workspace:^, @uppy/instagram@workspace:packages/@uppy/instagram":
version: 0.0.0-use.local
resolution: "@uppy/instagram@workspace:packages/@uppy/instagram"
@ -11206,7 +11213,7 @@ __metadata:
languageName: unknown
linkType: soft
"@uppy/transloadit@workspace:*, @uppy/transloadit@workspace:packages/@uppy/transloadit":
"@uppy/transloadit@workspace:*, @uppy/transloadit@workspace:^, @uppy/transloadit@workspace:packages/@uppy/transloadit":
version: 0.0.0-use.local
resolution: "@uppy/transloadit@workspace:packages/@uppy/transloadit"
dependencies:
@ -12619,12 +12626,12 @@ __metadata:
linkType: hard
"brace-expansion@npm:^1.1.7":
version: 1.1.12
resolution: "brace-expansion@npm:1.1.12"
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"
dependencies:
balanced-match: "npm:^1.0.0"
concat-map: "npm:0.0.1"
checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562
checksum: 10/faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07
languageName: node
linkType: hard
@ -13283,7 +13290,7 @@ __metadata:
languageName: node
linkType: hard
"combined-stream@npm:^1.0.8":
"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8":
version: 1.0.8
resolution: "combined-stream@npm:1.0.8"
dependencies:
@ -14264,9 +14271,9 @@ __metadata:
linkType: hard
"devalue@npm:^5.1.0":
version: 5.3.2
resolution: "devalue@npm:5.3.2"
checksum: 10/2e15e9ed6844e86f1d086088c0f51e447e2300ee385ace02acca4691563ca13e5d0f5445603afc52653609078039c9dad69bd8204dadfac0919fe1ef65f08a88
version: 5.1.1
resolution: "devalue@npm:5.1.1"
checksum: 10/ff36fe61af61636419eb16692d2fe43d793cdfb17f868bb3560c5485e4c25bc38d472304e61efdec6e806a3c4b450c46247decf59968b4a05ddc7714ea64f885
languageName: node
linkType: hard
@ -15296,11 +15303,11 @@ __metadata:
linkType: hard
"esquery@npm:^1.4.2":
version: 1.6.0
resolution: "esquery@npm:1.6.0"
version: 1.5.0
resolution: "esquery@npm:1.5.0"
dependencies:
estraverse: "npm:^5.1.0"
checksum: 10/c587fb8ec9ed83f2b1bc97cf2f6854cc30bf784a79d62ba08c6e358bf22280d69aee12827521cf38e69ae9761d23fb7fde593ce315610f85655c139d99b05e5a
checksum: 10/e65fcdfc1e0ff5effbf50fb4f31ea20143ae5df92bb2e4953653d8d40aa4bc148e0d06117a592ce4ea53eeab1dafdfded7ea7e22a5be87e82d73757329a1b01d
languageName: node
linkType: hard
@ -16322,16 +16329,13 @@ __metadata:
linkType: hard
"form-data@npm:^2.5.0":
version: 2.5.5
resolution: "form-data@npm:2.5.5"
version: 2.5.1
resolution: "form-data@npm:2.5.1"
dependencies:
asynckit: "npm:^0.4.0"
combined-stream: "npm:^1.0.8"
es-set-tostringtag: "npm:^2.1.0"
hasown: "npm:^2.0.2"
mime-types: "npm:^2.1.35"
safe-buffer: "npm:^5.2.1"
checksum: 10/4b6a8d07bb67089da41048e734215f68317a8e29dd5385a972bf5c458a023313c69d3b5d6b8baafbb7f808fa9881e0e2e030ffe61e096b3ddc894c516401271d
combined-stream: "npm:^1.0.6"
mime-types: "npm:^2.1.12"
checksum: 10/2e2e5e927979ba3623f9b4c4bcc939275fae3f2dea9dafc8db3ca656a3d75476605de2c80f0e6f1487987398e056f0b4c738972d6e1edd83392d5686d0952eed
languageName: node
linkType: hard
@ -17480,17 +17484,7 @@ __metadata:
languageName: node
linkType: hard
"import-fresh@npm:^3.2.1":
version: 3.3.1
resolution: "import-fresh@npm:3.3.1"
dependencies:
parent-module: "npm:^1.0.0"
resolve-from: "npm:^4.0.0"
checksum: 10/a06b19461b4879cc654d46f8a6244eb55eb053437afd4cbb6613cad6be203811849ed3e4ea038783092879487299fda24af932b86bdfff67c9055ba3612b8c87
languageName: node
linkType: hard
"import-fresh@npm:^3.3.0":
"import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0":
version: 3.3.0
resolution: "import-fresh@npm:3.3.0"
dependencies:
@ -19627,7 +19621,7 @@ __metadata:
languageName: node
linkType: hard
"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.26, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
"mime-types@npm:2.1.35, mime-types@npm:^2.1.12, mime-types@npm:^2.1.26, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
version: 2.1.35
resolution: "mime-types@npm:2.1.35"
dependencies:
@ -23366,7 +23360,7 @@ __metadata:
languageName: node
linkType: hard
"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0":
"safe-buffer@npm:5.2.1, safe-buffer@npm:>=5.1.0, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0":
version: 5.2.1
resolution: "safe-buffer@npm:5.2.1"
checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451
@ -25698,6 +25692,7 @@ __metadata:
"@uppy/google-drive-picker": "workspace:*"
"@uppy/google-photos-picker": "workspace:*"
"@uppy/image-editor": "workspace:*"
"@uppy/image-generator": "workspace:*"
"@uppy/instagram": "workspace:*"
"@uppy/locales": "workspace:*"
"@uppy/onedrive": "workspace:*"