@basementstudio/sanity-ai-image-plugin 0.1.1 → 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.
Files changed (98) hide show
  1. package/README.md +131 -124
  2. package/package.json +1 -1
  3. package/dist/index.d.ts +0 -3
  4. package/dist/index.d.ts.map +0 -1
  5. package/dist/index.js +0 -3
  6. package/dist/index.js.map +0 -1
  7. package/dist/presets/article-featured-image.d.ts +0 -3
  8. package/dist/presets/article-featured-image.d.ts.map +0 -1
  9. package/dist/presets/article-featured-image.js +0 -17
  10. package/dist/presets/article-featured-image.js.map +0 -1
  11. package/dist/server/constants.d.ts +0 -6
  12. package/dist/server/constants.d.ts.map +0 -1
  13. package/dist/server/constants.js +0 -17
  14. package/dist/server/constants.js.map +0 -1
  15. package/dist/server/handle-request.d.ts +0 -5
  16. package/dist/server/handle-request.d.ts.map +0 -1
  17. package/dist/server/handle-request.js +0 -122
  18. package/dist/server/handle-request.js.map +0 -1
  19. package/dist/server/types.d.ts +0 -28
  20. package/dist/server/types.d.ts.map +0 -1
  21. package/dist/server/types.js +0 -2
  22. package/dist/server/types.js.map +0 -1
  23. package/dist/server/utils.d.ts +0 -50
  24. package/dist/server/utils.d.ts.map +0 -1
  25. package/dist/server/utils.js +0 -274
  26. package/dist/server/utils.js.map +0 -1
  27. package/dist/server.d.ts +0 -3
  28. package/dist/server.d.ts.map +0 -1
  29. package/dist/server.js +0 -3
  30. package/dist/server.js.map +0 -1
  31. package/dist/studio/components/asset-source.d.ts +0 -4
  32. package/dist/studio/components/asset-source.d.ts.map +0 -1
  33. package/dist/studio/components/asset-source.js +0 -169
  34. package/dist/studio/components/asset-source.js.map +0 -1
  35. package/dist/studio/components/frontend-rate-limit.d.ts +0 -29
  36. package/dist/studio/components/frontend-rate-limit.d.ts.map +0 -1
  37. package/dist/studio/components/frontend-rate-limit.js +0 -197
  38. package/dist/studio/components/frontend-rate-limit.js.map +0 -1
  39. package/dist/studio/components/generate-button-input.d.ts +0 -8
  40. package/dist/studio/components/generate-button-input.d.ts.map +0 -1
  41. package/dist/studio/components/generate-button-input.js +0 -194
  42. package/dist/studio/components/generate-button-input.js.map +0 -1
  43. package/dist/studio/components/input-router.d.ts +0 -7
  44. package/dist/studio/components/input-router.d.ts.map +0 -1
  45. package/dist/studio/components/input-router.js +0 -21
  46. package/dist/studio/components/input-router.js.map +0 -1
  47. package/dist/studio/files.d.ts +0 -9
  48. package/dist/studio/files.d.ts.map +0 -1
  49. package/dist/studio/files.js +0 -86
  50. package/dist/studio/files.js.map +0 -1
  51. package/dist/studio/frontend-rate-limit.d.ts +0 -29
  52. package/dist/studio/frontend-rate-limit.d.ts.map +0 -1
  53. package/dist/studio/frontend-rate-limit.js +0 -197
  54. package/dist/studio/frontend-rate-limit.js.map +0 -1
  55. package/dist/studio/plugin.d.ts +0 -3
  56. package/dist/studio/plugin.d.ts.map +0 -1
  57. package/dist/studio/plugin.js +0 -47
  58. package/dist/studio/plugin.js.map +0 -1
  59. package/dist/studio/settings/schema.d.ts +0 -8
  60. package/dist/studio/settings/schema.d.ts.map +0 -1
  61. package/dist/studio/settings/schema.js +0 -110
  62. package/dist/studio/settings/schema.js.map +0 -1
  63. package/dist/studio/settings/tool.d.ts +0 -3
  64. package/dist/studio/settings/tool.d.ts.map +0 -1
  65. package/dist/studio/settings/tool.js +0 -292
  66. package/dist/studio/settings/tool.js.map +0 -1
  67. package/dist/studio/settings-data.d.ts +0 -24
  68. package/dist/studio/settings-data.d.ts.map +0 -1
  69. package/dist/studio/settings-data.js +0 -99
  70. package/dist/studio/settings-data.js.map +0 -1
  71. package/dist/studio/shared-secret.d.ts +0 -9
  72. package/dist/studio/shared-secret.d.ts.map +0 -1
  73. package/dist/studio/shared-secret.js +0 -37
  74. package/dist/studio/shared-secret.js.map +0 -1
  75. package/dist/utils/config.d.ts +0 -3
  76. package/dist/utils/config.d.ts.map +0 -1
  77. package/dist/utils/config.js +0 -59
  78. package/dist/utils/config.js.map +0 -1
  79. package/dist/utils/context-fields.d.ts +0 -16
  80. package/dist/utils/context-fields.d.ts.map +0 -1
  81. package/dist/utils/context-fields.js +0 -104
  82. package/dist/utils/context-fields.js.map +0 -1
  83. package/dist/utils/document-paths.d.ts +0 -10
  84. package/dist/utils/document-paths.d.ts.map +0 -1
  85. package/dist/utils/document-paths.js +0 -33
  86. package/dist/utils/document-paths.js.map +0 -1
  87. package/dist/utils/models.d.ts +0 -22
  88. package/dist/utils/models.d.ts.map +0 -1
  89. package/dist/utils/models.js +0 -76
  90. package/dist/utils/models.js.map +0 -1
  91. package/dist/utils/prompts.d.ts +0 -2
  92. package/dist/utils/prompts.d.ts.map +0 -1
  93. package/dist/utils/prompts.js +0 -7
  94. package/dist/utils/prompts.js.map +0 -1
  95. package/dist/utils/shared.d.ts +0 -96
  96. package/dist/utils/shared.d.ts.map +0 -1
  97. package/dist/utils/shared.js +0 -17
  98. package/dist/utils/shared.js.map +0 -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
- This package is intentionally self-contained so it can be copied into a new repo
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 { defineConfig } from "sanity";
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: "home-page-featured-image",
28
+ id: "article-featured-image",
61
29
  type: "generateButton",
62
- title: "Home Page Featured Image",
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
- ### 2. Add the thin app-owned route.
39
+ With that config:
74
40
 
75
- ```ts
76
- import {
77
- handleAiImageRequest,
78
- SUPPORTED_AI_IMAGE_MODELS,
79
- } from "sanity-ai-image-plugin/server";
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
- const allowedModels = [
82
- SUPPORTED_AI_IMAGE_MODELS[0],
83
- SUPPORTED_AI_IMAGE_MODELS[2],
84
- ] as const;
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
- allowedModels: [...allowedModels],
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
- ### 3. Set env vars
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
- Open the AI Image Plugin settings tool and configure the same shared secret
111
- value there.
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 plugin stores that Studio-side value with `@sanity/studio-secrets`, so it
114
- is fetched at runtime for logged-in Studio users instead of being bundled into
115
- the Studio source code.
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
- route's configured `allowedModels`
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@basementstudio/sanity-ai-image-plugin",
3
3
  "private": false,
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "packageManager": "bun@1.3.7",
6
6
  "description": "Portable Sanity Studio plugin and server helper for AI image generation.",
7
7
  "type": "module",
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export { SUPPORTED_AI_IMAGE_MODELS } from "./utils/models";
2
- export { aiImagePlugin } from "./studio/plugin";
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js DELETED
@@ -1,3 +0,0 @@
1
- export { SUPPORTED_AI_IMAGE_MODELS } from "./utils/models";
2
- export { aiImagePlugin } from "./studio/plugin";
3
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
@@ -1,3 +0,0 @@
1
- import type { GenerateButtonTarget } from "../utils/shared";
2
- export declare function createArticleFeaturedImageTarget(options?: Partial<Omit<GenerateButtonTarget, "type">>): GenerateButtonTarget;
3
- //# sourceMappingURL=article-featured-image.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"article-featured-image.d.ts","sourceRoot":"","sources":["../../src/presets/article-featured-image.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAE3D,wBAAgB,gCAAgC,CAC9C,OAAO,GAAE,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAM,GACxD,oBAAoB,CAkBtB"}
@@ -1,17 +0,0 @@
1
- export function createArticleFeaturedImageTarget(options = {}) {
2
- return {
3
- id: options.id || "article-featured-image",
4
- type: "generateButton",
5
- title: options.title || "Article Featured Image",
6
- description: options.description ||
7
- "Adds a Generate button that combines shared art direction, article context, and an optional editor prompt.",
8
- dialogTitle: options.dialogTitle || "Generate Featured Image",
9
- documentType: options.documentType || "article",
10
- fieldPath: options.fieldPath || "featuredImage",
11
- promptLabel: options.promptLabel || "Custom prompt",
12
- promptPlaceholder: options.promptPlaceholder ||
13
- "Optional custom instructions for this article image.",
14
- suggestedContextFieldPaths: options.suggestedContextFieldPaths || ["title", "excerpt"],
15
- };
16
- }
17
- //# sourceMappingURL=article-featured-image.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"article-featured-image.js","sourceRoot":"","sources":["../../src/presets/article-featured-image.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gCAAgC,CAC9C,UAAuD,EAAE;IAEzD,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,wBAAwB;QAC1C,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,wBAAwB;QAChD,WAAW,EACT,OAAO,CAAC,WAAW;YACnB,4GAA4G;QAC9G,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,yBAAyB;QAC7D,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,SAAS;QAC/C,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,eAAe;QAC/C,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,eAAe;QACnD,iBAAiB,EACf,OAAO,CAAC,iBAAiB;YACzB,sDAAsD;QACxD,0BAA0B,EACxB,OAAO,CAAC,0BAA0B,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC;KAC7D,CAAA;AACH,CAAC"}
@@ -1,6 +0,0 @@
1
- export declare const DEFAULT_MODEL: "gemini-2.5-flash-image" | "gemini-3.1-flash-image-preview" | "gpt-image-1";
2
- export declare const DEFAULT_PROVIDER_API_URL = "https://generativelanguage.googleapis.com/v1beta";
3
- export declare const DEFAULT_MAX_REFERENCE_FILE_BYTES: number;
4
- export declare const SUPPORTED_REFERENCE_IMAGE_TYPES: Set<string>;
5
- export declare const OPENAI_SUPPORTED_REFERENCE_IMAGE_TYPES: Set<string>;
6
- //# sourceMappingURL=constants.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/server/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,6EAAmC,CAAC;AAC9D,eAAO,MAAM,wBAAwB,qDACe,CAAC;AACrD,eAAO,MAAM,gCAAgC,QAAkB,CAAC;AAEhE,eAAO,MAAM,+BAA+B,aAM1C,CAAC;AAEH,eAAO,MAAM,sCAAsC,aAIjD,CAAC"}
@@ -1,17 +0,0 @@
1
- import { DEFAULT_SUPPORTED_AI_IMAGE_MODEL } from "../utils/models";
2
- export const DEFAULT_MODEL = DEFAULT_SUPPORTED_AI_IMAGE_MODEL;
3
- export const DEFAULT_PROVIDER_API_URL = "https://generativelanguage.googleapis.com/v1beta";
4
- export const DEFAULT_MAX_REFERENCE_FILE_BYTES = 8 * 1024 * 1024;
5
- export const SUPPORTED_REFERENCE_IMAGE_TYPES = new Set([
6
- "image/heic",
7
- "image/heif",
8
- "image/jpeg",
9
- "image/png",
10
- "image/webp",
11
- ]);
12
- export const OPENAI_SUPPORTED_REFERENCE_IMAGE_TYPES = new Set([
13
- "image/jpeg",
14
- "image/png",
15
- "image/webp",
16
- ]);
17
- //# sourceMappingURL=constants.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/server/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,MAAM,iBAAiB,CAAC;AAEnE,MAAM,CAAC,MAAM,aAAa,GAAG,gCAAgC,CAAC;AAC9D,MAAM,CAAC,MAAM,wBAAwB,GACnC,kDAAkD,CAAC;AACrD,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhE,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAC;IACrD,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,YAAY;CACb,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,sCAAsC,GAAG,IAAI,GAAG,CAAC;IAC5D,YAAY;IACZ,WAAW;IACX,YAAY;CACb,CAAC,CAAC"}
@@ -1,5 +0,0 @@
1
- import type { RequestOptions } from "./types";
2
- export { DEFAULT_MAX_REFERENCE_FILE_BYTES, DEFAULT_MODEL, DEFAULT_PROVIDER_API_URL, SUPPORTED_REFERENCE_IMAGE_TYPES, } from "./constants";
3
- export type { RequestOptions, SuccessResponse } from "./types";
4
- export declare function handleAiImageRequest(request: Request, options: RequestOptions): Promise<Response>;
5
- //# sourceMappingURL=handle-request.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"handle-request.d.ts","sourceRoot":"","sources":["../../src/server/handle-request.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,SAAS,CAAC;AAC/D,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,wBAAwB,EACxB,+BAA+B,GAChC,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/D,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,QAAQ,CAAC,CA4LnB"}
@@ -1,122 +0,0 @@
1
- import { isSupportedAiImageModelId, resolveAllowedAiImageModel, SUPPORTED_AI_IMAGE_MODELS, } from "../utils/models";
2
- import { generateImageWithModel, getErrorMessage, getErrorStatusCode, getFormRequestPayload, getMissingApiKeyErrorMessage, getProviderApiKey, getProviderDisplayName, getReferenceImageValidationError, getSameOriginValidationError, getSharedSecretValidationError, isMultipartFormData, jsonResponse, parseHeaderByteLength, resolveRequestOptions, } from "./utils";
3
- export { DEFAULT_MAX_REFERENCE_FILE_BYTES, DEFAULT_MODEL, DEFAULT_PROVIDER_API_URL, SUPPORTED_REFERENCE_IMAGE_TYPES, } from "./constants";
4
- export async function handleAiImageRequest(request, options) {
5
- let requestOptions;
6
- // Normalize server config up front so the rest of the handler can assume a
7
- // single, fully-resolved set of limits and model choices.
8
- try {
9
- requestOptions = resolveRequestOptions(options);
10
- }
11
- catch (error) {
12
- return jsonResponse({
13
- error: getErrorMessage(error) ||
14
- "AI Image Plugin server configuration is invalid.",
15
- }, 500);
16
- }
17
- const sharedSecretError = getSharedSecretValidationError(request, requestOptions.sharedSecret);
18
- if (sharedSecretError) {
19
- return jsonResponse({
20
- error: sharedSecretError.error,
21
- }, sharedSecretError.status);
22
- }
23
- // Reject browser requests that do not come from the same Sanity Studio
24
- // origin when same-origin enforcement is enabled.
25
- if (requestOptions.enforceSameOrigin) {
26
- const sameOriginError = getSameOriginValidationError(request);
27
- if (sameOriginError) {
28
- return jsonResponse({
29
- error: sameOriginError.error,
30
- }, sameOriginError.status);
31
- }
32
- }
33
- // The endpoint only accepts multipart form submissions because prompt/model
34
- // metadata and uploaded reference images arrive together.
35
- if (!isMultipartFormData(request.headers.get("content-type"))) {
36
- return jsonResponse({
37
- error: "AI Image Plugin requires a multipart/form-data request.",
38
- }, 415);
39
- }
40
- // Fail early on obviously oversized requests before parsing the body.
41
- const contentLength = parseHeaderByteLength(request.headers.get("content-length"));
42
- if (contentLength !== null &&
43
- contentLength > requestOptions.maxTotalReferenceBytes) {
44
- return jsonResponse({
45
- error: `AI Image Plugin requests cannot exceed ${requestOptions.maxTotalReferenceBytes} bytes.`,
46
- }, 413);
47
- }
48
- let formData;
49
- // Parse the multipart payload into the prompt, optional model override, and
50
- // the capped list of uploaded reference images.
51
- try {
52
- formData = await request.formData();
53
- }
54
- catch {
55
- return jsonResponse({
56
- error: "AI Image Plugin could not read the upload payload.",
57
- }, 400);
58
- }
59
- const { prompt, referenceImages, requestedModel } = getFormRequestPayload(formData, requestOptions.maxReferences);
60
- if (!prompt) {
61
- return jsonResponse({ error: "Prompt is required." }, 400);
62
- }
63
- // Resolve the effective model, then make sure it is both supported by the
64
- // plugin and allowed by this installation's server config.
65
- const modelId = requestedModel || requestOptions.defaultModel;
66
- if (!isSupportedAiImageModelId(modelId)) {
67
- return jsonResponse({
68
- error: `AI Image Plugin does not support the model "${modelId}". Supported models are: ${SUPPORTED_AI_IMAGE_MODELS.join(", ")}.`,
69
- }, 400);
70
- }
71
- if (!requestOptions.allowedModels.includes(modelId)) {
72
- return jsonResponse({
73
- error: `AI Image Plugin does not allow the model "${modelId}" for this installation.`,
74
- }, 400);
75
- }
76
- const selectedModel = resolveAllowedAiImageModel(modelId, requestOptions.allowedModels);
77
- const referenceImageError = getReferenceImageValidationError({
78
- maxReferenceFileBytes: requestOptions.maxReferenceFileBytes,
79
- maxTotalReferenceBytes: requestOptions.maxTotalReferenceBytes,
80
- model: selectedModel,
81
- referenceImages,
82
- });
83
- if (referenceImageError) {
84
- return jsonResponse({
85
- error: referenceImageError.error,
86
- }, referenceImageError.status);
87
- }
88
- // Pick the provider credentials that match the selected model family.
89
- const apiKey = getProviderApiKey(selectedModel, options);
90
- if (!apiKey) {
91
- return jsonResponse({
92
- error: getMissingApiKeyErrorMessage(selectedModel),
93
- }, 500);
94
- }
95
- // Delegate the actual image generation call and translate provider failures
96
- // into a stable API response shape for the Studio client.
97
- try {
98
- const generatedImage = await generateImageWithModel({
99
- apiKey,
100
- model: selectedModel,
101
- prompt,
102
- referenceImages,
103
- ...(options.geminiApiUrl ? { geminiApiUrl: options.geminiApiUrl } : {}),
104
- });
105
- if (!generatedImage) {
106
- return jsonResponse({
107
- error: "AI Image Plugin returned a response, but no generated image was found.",
108
- }, 502);
109
- }
110
- return jsonResponse({
111
- ...generatedImage,
112
- model: selectedModel,
113
- });
114
- }
115
- catch (error) {
116
- return jsonResponse({
117
- error: getErrorMessage(error) ||
118
- `AI Image Plugin could not reach ${getProviderDisplayName(selectedModel)}.`,
119
- }, getErrorStatusCode(error) || 502);
120
- }
121
- }
122
- //# sourceMappingURL=handle-request.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"handle-request.js","sourceRoot":"","sources":["../../src/server/handle-request.ts"],"names":[],"mappings":"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"}
@@ -1,28 +0,0 @@
1
- import type { SupportedAiImageModelId } from "../utils/models";
2
- export type GenerateImageFunction = (options: Record<string, unknown>) => Promise<{
3
- image?: unknown;
4
- }>;
5
- export type GeneratedImagePayload = {
6
- base64?: string;
7
- mediaType?: string;
8
- uint8Array?: Uint8Array;
9
- };
10
- export type SuccessResponse = {
11
- data: string;
12
- mimeType: string;
13
- model: string;
14
- };
15
- export type RequestOptions = {
16
- allowedModels?: SupportedAiImageModelId[];
17
- apiKey?: string;
18
- enforceSameOrigin?: boolean;
19
- geminiApiUrl?: string;
20
- googleApiKey?: string;
21
- maxReferenceFileBytes?: number;
22
- maxReferences?: number;
23
- maxTotalReferenceBytes?: number;
24
- model?: SupportedAiImageModelId;
25
- openAiApiKey?: string;
26
- sharedSecret: string;
27
- };
28
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE/D,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;IAChF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,CAAC,EAAE,uBAAuB,EAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":""}