@basementstudio/sanity-ai-image-plugin 0.1.2 → 0.1.3
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/README.md +131 -124
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,115 +4,171 @@ Portable Sanity Studio plugin and server helper for generating images with
|
|
|
4
4
|
Gemini, OpenAI, and other package-supported image models and dropping the
|
|
5
5
|
result straight into Sanity image fields.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
or published later without dragging along an app-specific schema layout.
|
|
9
|
-
|
|
10
|
-
This repo is source-first while it is being developed here:
|
|
11
|
-
|
|
12
|
-
- package exports point at `src/*`
|
|
13
|
-
|
|
14
|
-
## What It Owns
|
|
15
|
-
|
|
16
|
-
- a Studio plugin via `aiImagePlugin(...)`
|
|
17
|
-
- a plugin-owned settings document and settings tool
|
|
18
|
-
- a generic image asset source
|
|
19
|
-
- optional generate-button targets for specific image fields
|
|
20
|
-
- a server helper export for the app-owned API route
|
|
21
|
-
|
|
22
|
-
## Install Shape
|
|
23
|
-
|
|
24
|
-
The package exposes two entrypoints:
|
|
25
|
-
|
|
26
|
-
- `sanity-ai-image-plugin`
|
|
27
|
-
- `sanity-ai-image-plugin/server`
|
|
28
|
-
|
|
29
|
-
## Consumer Setup
|
|
7
|
+
## Setup
|
|
30
8
|
|
|
31
9
|
### 1. Add the Studio plugin
|
|
32
10
|
|
|
11
|
+
1. Import `aiImagePlugin` in your `sanity.config.ts`.
|
|
12
|
+
2. Add it to the `plugins` array.
|
|
13
|
+
3. Set `apiVersion`.
|
|
14
|
+
4. Add a `generateButton` target for each image field that should show a
|
|
15
|
+
`Generate` button.
|
|
16
|
+
|
|
33
17
|
```ts
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
SUPPORTED_AI_IMAGE_MODELS,
|
|
37
|
-
createArticleFeaturedImageTarget,
|
|
38
|
-
aiImagePlugin,
|
|
39
|
-
} from "sanity-ai-image-plugin";
|
|
40
|
-
|
|
41
|
-
const allowedModels = [
|
|
42
|
-
SUPPORTED_AI_IMAGE_MODELS[0],
|
|
43
|
-
SUPPORTED_AI_IMAGE_MODELS[2],
|
|
44
|
-
] as const;
|
|
18
|
+
import { aiImagePlugin } from "@basementstudio/sanity-ai-image-plugin"
|
|
19
|
+
import { defineConfig } from "sanity"
|
|
45
20
|
|
|
46
21
|
export default defineConfig({
|
|
47
22
|
// ...your existing config
|
|
48
23
|
plugins: [
|
|
49
24
|
aiImagePlugin({
|
|
50
25
|
apiVersion: "2025-02-19",
|
|
51
|
-
allowedModels: [...allowedModels],
|
|
52
|
-
assetSource: true,
|
|
53
|
-
frontendRateLimit: {
|
|
54
|
-
maxRequests: 3,
|
|
55
|
-
windowSecs: 60,
|
|
56
|
-
},
|
|
57
26
|
targets: [
|
|
58
|
-
createArticleFeaturedImageTarget(),
|
|
59
27
|
{
|
|
60
|
-
id: "
|
|
28
|
+
id: "article-featured-image",
|
|
61
29
|
type: "generateButton",
|
|
62
|
-
|
|
63
|
-
documentType: "homePage",
|
|
30
|
+
documentType: "article",
|
|
64
31
|
fieldPath: "featuredImage",
|
|
65
|
-
suggestedContextFieldPaths: ["title", "description"],
|
|
66
32
|
},
|
|
67
33
|
],
|
|
68
34
|
}),
|
|
69
35
|
],
|
|
70
|
-
})
|
|
36
|
+
})
|
|
71
37
|
```
|
|
72
38
|
|
|
73
|
-
|
|
39
|
+
With that config:
|
|
74
40
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
41
|
+
- the AI Image Plugin settings tool is registered in Studio
|
|
42
|
+
- the default asset source is enabled
|
|
43
|
+
- `article.featuredImage` gets a `Generate` button
|
|
44
|
+
|
|
45
|
+
#### `aiImagePlugin(...)` options
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
| Option | Required | Default | Notes |
|
|
49
|
+
| ------------------- | -------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `apiVersion` | Yes | none | Sanity API version used by the plugin's client calls. |
|
|
51
|
+
| `targets` | No | `[]` | Generate-button targets. Each matching target adds a `Generate` button above a Sanity image input. |
|
|
52
|
+
| `allowedModels` | No | `["gemini-2.5-flash-image"]` | Non-empty subset of `SUPPORTED_AI_IMAGE_MODELS`. The first entry becomes the default model in Studio unless settings or the route override it. |
|
|
53
|
+
| `apiEndpoint` | No | `"/api/ai-image-plugin"` | Path the Studio calls when generating images. Change this if your route lives somewhere else. |
|
|
54
|
+
| `assetSource` | No | enabled with built-in defaults | Adds the plugin to the Sanity image asset picker. Set `false` to disable it, or pass an object to customize labels and copy. |
|
|
55
|
+
| `frontendRateLimit` | No | `{ maxRequests: 3, windowSecs: 60 }` | Browser-side rolling window limiter shared across tabs via `localStorage`. Set `false` to disable it. |
|
|
56
|
+
| `settingsTool` | No | `{ name: "ai-image-plugin-settings", title: "AI Image Plugin", registerSchemaType: false }` | Lets you rename the settings tool or opt into registering the schema type automatically. |
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
#### `targets[]` generate-button fields
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
| Field | Required | Default | Notes |
|
|
63
|
+
| ---------------------------- | -------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `id` | Yes | none | Stable target ID. This is also how per-target settings are stored. |
|
|
65
|
+
| `type` | Yes | none | Must be `"generateButton"`. |
|
|
66
|
+
| `documentType` | Yes | none | Sanity document type the target should match, like `"article"` or `"homePage"`. |
|
|
67
|
+
| `fieldPath` | Yes | none | Dot path to the image field, like `"featuredImage"` or `"seo.ogImage"`. |
|
|
68
|
+
| `title` | No | none | Friendly name shown in the settings tool and used as a fallback label in parts of the UI. |
|
|
69
|
+
| `description` | No | `"Generate an image with AI Image Plugin for this field."` in the field UI | Short helper copy shown above the button and in the settings tool. |
|
|
70
|
+
| `dialogTitle` | No | `"Generate Image"` | Title shown at the top of the generate dialog. |
|
|
71
|
+
| `promptLabel` | No | `"Custom prompt"` | Label for the freeform editor prompt field in the dialog. |
|
|
72
|
+
| `promptPlaceholder` | No | `"Optional custom instructions for this image."` | Placeholder text for the editor prompt field. |
|
|
73
|
+
| `suggestedContextFieldPaths` | No | `[]` | Top-level document fields that should be preselected as context tags when the dialog opens. Editors can still toggle them per generation. |
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
#### `assetSource` fields
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
| Field | Required | Default | Notes |
|
|
80
|
+
| ------------------- | -------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
81
|
+
| `enabled` | No | `true` | Set `false` to disable the asset source while keeping the rest of the plugin enabled. |
|
|
82
|
+
| `id` | No | `"ai-image-plugin-asset-source"` | Stable target ID used for asset-source-specific settings. |
|
|
83
|
+
| `title` | No | `"Image Asset Source"` | Label shown in the asset picker. |
|
|
84
|
+
| `description` | No | `"Adds AI Image Plugin to the image asset picker."` | Helper text shown in the settings UI. |
|
|
85
|
+
| `promptLabel` | No | `"Prompt"` | Label for the asset source prompt field. |
|
|
86
|
+
| `promptPlaceholder` | No | `"Turn these references into a clean homepage hero image with warm daylight and subtle depth."` | Placeholder text for the asset source prompt field. |
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
#### `frontendRateLimit` fields
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
| Field | Required | Default | Notes |
|
|
93
|
+
| ------------- | -------- | ------- | ----------------------------------------------------------------- |
|
|
94
|
+
| `maxRequests` | No | `3` | Maximum generation attempts allowed during the configured window. |
|
|
95
|
+
| `windowSecs` | No | `60` | Rolling window length in seconds. |
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
#### `settingsTool` fields
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
| Field | Required | Default | Notes |
|
|
102
|
+
| -------------------- | -------- | ---------------------------- | ---------------------------------------------------------------------------------------- |
|
|
103
|
+
| `name` | No | `"ai-image-plugin-settings"` | Internal tool name. |
|
|
104
|
+
| `title` | No | `"AI Image Plugin"` | Title shown in the Studio tool menu. |
|
|
105
|
+
| `registerSchemaType` | No | `false` | Set `true` if you want the plugin to register its settings document schema type for you. |
|
|
80
106
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
107
|
+
|
|
108
|
+
### 2. Add the API route
|
|
109
|
+
|
|
110
|
+
1. Create a POST route at `/api/ai-image-plugin`.
|
|
111
|
+
2. Import `handleAiImageRequest` from the server entrypoint.
|
|
112
|
+
3. Pass the shared secret and the provider API key(s) for the models you allow.
|
|
113
|
+
4. If you customize `allowedModels` in Studio, mirror that same list here.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { handleAiImageRequest } from "@basementstudio/sanity-ai-image-plugin/server"
|
|
85
117
|
|
|
86
118
|
export async function POST(request: Request) {
|
|
87
119
|
return handleAiImageRequest(request, {
|
|
88
|
-
|
|
89
|
-
apiKey: process.env.GEMINI_API_KEY,
|
|
90
|
-
openAiApiKey: process.env.OPENAI_API_KEY,
|
|
120
|
+
apiKey: process.env.GEMINI_API_KEY!,
|
|
91
121
|
sharedSecret: process.env.AI_IMAGE_PLUGIN_SHARED_SECRET!,
|
|
92
|
-
// Shared-secret auth and same-origin protection are enabled by default.
|
|
93
|
-
// Optional overrides:
|
|
94
|
-
// model: process.env.AI_IMAGE_MODEL as (typeof allowedModels)[number],
|
|
95
|
-
// maxReferenceFileBytes: 8 * 1024 * 1024,
|
|
96
|
-
// maxTotalReferenceBytes: 5 * 8 * 1024 * 1024,
|
|
97
122
|
})
|
|
98
123
|
}
|
|
99
124
|
```
|
|
100
125
|
|
|
101
|
-
|
|
126
|
+
If your route lives somewhere else, set `apiEndpoint` in `aiImagePlugin(...)`
|
|
127
|
+
to match it.
|
|
128
|
+
|
|
129
|
+
#### `handleAiImageRequest(request, options)` fields
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
| Field | Required | Default | Notes |
|
|
133
|
+
| ------------------------ | ------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
134
|
+
| `sharedSecret` | Yes | none | Must exactly match the secret saved in Studio. |
|
|
135
|
+
| `apiKey` | Conditionally | none | Google API key. Use this for Google models, or use `googleApiKey` instead. |
|
|
136
|
+
| `googleApiKey` | Conditionally | none | Provider-specific alias for Google models. If both `googleApiKey` and `apiKey` are present, `googleApiKey` wins. |
|
|
137
|
+
| `openAiApiKey` | Conditionally | none | Required when you allow OpenAI models such as `gpt-image-1`. |
|
|
138
|
+
| `allowedModels` | No | If `model` is set, `[model]`; otherwise `["gemini-2.5-flash-image"]` | Non-empty subset of the package-supported models. Keep this in sync with the Studio config if you expose multiple models. |
|
|
139
|
+
| `model` | No | First entry in resolved `allowedModels` | Route-level default model. It must also be present in `allowedModels`. |
|
|
140
|
+
| `enforceSameOrigin` | No | `true` | Rejects browser requests whose `Origin` does not match the API route origin. |
|
|
141
|
+
| `geminiApiUrl` | No | Google Generative Language API default base URL | Optional override for Google requests. |
|
|
142
|
+
| `maxReferenceFileBytes` | No | `8 * 1024 * 1024` | Maximum size for a single reference image upload. |
|
|
143
|
+
| `maxReferences` | No | `5` | Maximum number of reference images accepted per request. |
|
|
144
|
+
| `maxTotalReferenceBytes` | No | `maxReferences * maxReferenceFileBytes` | Maximum combined size of all reference images in one request. |
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
### 3. Set the env vars your route uses
|
|
148
|
+
|
|
149
|
+
Add the variables your server route reads. For the minimal example above, you
|
|
150
|
+
only need the shared secret and a Google API key.
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
| Variable | Required | Default | Notes |
|
|
154
|
+
| ------------------------------- | ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
155
|
+
| `AI_IMAGE_PLUGIN_SHARED_SECRET` | Yes | none | Shared secret used by your app route. Use a long random string. |
|
|
156
|
+
| `GEMINI_API_KEY` | Conditionally | none | Required when the active model is a Google model such as `gemini-2.5-flash-image`. |
|
|
157
|
+
| `OPENAI_API_KEY` | Conditionally | none | Required when the active model is an OpenAI model such as `gpt-image-1`. |
|
|
158
|
+
| `AI_IMAGE_MODEL` | No | none | Optional project-level env var you can read in your own route if you want to choose the default model from the environment. The plugin does not read this automatically. |
|
|
102
159
|
|
|
103
|
-
- `GEMINI_API_KEY` is required when you allow Google models
|
|
104
|
-
- `OPENAI_API_KEY` is required when you allow OpenAI models
|
|
105
|
-
- `AI_IMAGE_PLUGIN_SHARED_SECRET` is required for the app-owned route
|
|
106
|
-
- `AI_IMAGE_MODEL` is optional and can still override the server default
|
|
107
160
|
|
|
108
161
|
### 4. Configure the shared secret in Studio
|
|
109
162
|
|
|
110
|
-
|
|
111
|
-
|
|
163
|
+
1. Start your Sanity Studio.
|
|
164
|
+
2. Open the `AI Image Plugin` tool in the Studio navigation.
|
|
165
|
+
3. Click `Configure shared secret`.
|
|
166
|
+
4. Paste the same value you set in `AI_IMAGE_PLUGIN_SHARED_SECRET`.
|
|
167
|
+
5. Save it.
|
|
112
168
|
|
|
113
|
-
The
|
|
114
|
-
|
|
115
|
-
the
|
|
169
|
+
The Studio stores this secret separately from the normal plugin settings
|
|
170
|
+
document and sends it as the `x-ai-image-plugin-secret` header when
|
|
171
|
+
generating images. If you ever rotate the secret, update it in both places.
|
|
116
172
|
|
|
117
173
|
## Supported Models
|
|
118
174
|
|
|
@@ -128,26 +184,6 @@ ordered `allowedModels` config. The first allowed model becomes the fallback
|
|
|
128
184
|
default for both the Studio UI and the server helper unless the settings
|
|
129
185
|
document or route overrides it.
|
|
130
186
|
|
|
131
|
-
## Settings Model
|
|
132
|
-
|
|
133
|
-
The plugin owns one settings document:
|
|
134
|
-
|
|
135
|
-
- `_id`: `aiImagePlugin.settings`
|
|
136
|
-
- `_type`: `aiImagePluginSettings`
|
|
137
|
-
|
|
138
|
-
It stores:
|
|
139
|
-
|
|
140
|
-
- `globalModel`
|
|
141
|
-
- `globalPrompt`
|
|
142
|
-
- `globalReferenceImages`
|
|
143
|
-
- `targetConfigs[]`
|
|
144
|
-
|
|
145
|
-
Each target config can override:
|
|
146
|
-
|
|
147
|
-
- `targetId`
|
|
148
|
-
- `prompt`
|
|
149
|
-
- `referenceImages`
|
|
150
|
-
|
|
151
187
|
## Behavior
|
|
152
188
|
|
|
153
189
|
### Server helper
|
|
@@ -175,7 +211,7 @@ By default it also enforces:
|
|
|
175
211
|
- `8 MiB` maximum per reference image
|
|
176
212
|
- a combined reference-image cap of `maxReferences * 8 MiB`
|
|
177
213
|
- the requested `model` must be both package-supported and present in the
|
|
178
|
-
|
|
214
|
+
route's configured `allowedModels`
|
|
179
215
|
|
|
180
216
|
If your framework supports route-level body limits, keep those enabled too.
|
|
181
217
|
|
|
@@ -260,27 +296,6 @@ Selected document context is built as generic lines such as:
|
|
|
260
296
|
|
|
261
297
|
- `The field called "title" has content "...".`
|
|
262
298
|
|
|
263
|
-
## Optional Preset
|
|
264
|
-
|
|
265
|
-
`createArticleFeaturedImageTarget(...)` is an optional preset for
|
|
266
|
-
`article.featuredImage`.
|
|
267
|
-
|
|
268
|
-
It feeds the model:
|
|
269
|
-
|
|
270
|
-
- shared global settings
|
|
271
|
-
- target-specific article settings
|
|
272
|
-
- editor-selectable document-derived title + excerpt context suggestions
|
|
273
|
-
- optional editor prompt
|
|
274
|
-
|
|
275
|
-
If you do not use that preset, the package still works as a generic asset source
|
|
276
|
-
and generic generate-button plugin.
|
|
277
|
-
|
|
278
|
-
## Desk Structure
|
|
279
|
-
|
|
280
|
-
The plugin does not require custom desk structure wiring. If a consuming app
|
|
281
|
-
uses a custom structure and wants to hide `aiImagePluginSettings` from the normal
|
|
282
|
-
document list, that is optional and app-owned.
|
|
283
|
-
|
|
284
299
|
## PNG Normalization
|
|
285
300
|
|
|
286
301
|
All reference images are converted to PNG before they are sent to the server
|
|
@@ -290,11 +305,3 @@ helper. That includes:
|
|
|
290
305
|
- stored settings images downloaded from Sanity
|
|
291
306
|
- new images uploaded through the settings tool
|
|
292
307
|
|
|
293
|
-
## Development
|
|
294
|
-
|
|
295
|
-
```sh
|
|
296
|
-
bun run check
|
|
297
|
-
bun run build
|
|
298
|
-
bun test
|
|
299
|
-
```
|
|
300
|
-
|
package/package.json
CHANGED