@basementstudio/sanity-ai-image-plugin 0.0.0 → 0.1.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 (110) hide show
  1. package/README.md +281 -257
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +3 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/presets/article-featured-image.d.ts +3 -0
  7. package/dist/presets/article-featured-image.d.ts.map +1 -0
  8. package/dist/presets/article-featured-image.js +17 -0
  9. package/dist/presets/article-featured-image.js.map +1 -0
  10. package/dist/server/constants.d.ts +6 -0
  11. package/dist/server/constants.d.ts.map +1 -0
  12. package/{src/server/constants.ts → dist/server/constants.js} +17 -20
  13. package/dist/server/constants.js.map +1 -0
  14. package/dist/server/handle-request.d.ts +5 -0
  15. package/dist/server/handle-request.d.ts.map +1 -0
  16. package/dist/server/handle-request.js +122 -0
  17. package/dist/server/handle-request.js.map +1 -0
  18. package/dist/server/types.d.ts +28 -0
  19. package/dist/server/types.d.ts.map +1 -0
  20. package/dist/server/types.js +2 -0
  21. package/dist/server/types.js.map +1 -0
  22. package/dist/server/utils.d.ts +50 -0
  23. package/dist/server/utils.d.ts.map +1 -0
  24. package/dist/server/utils.js +274 -0
  25. package/dist/server/utils.js.map +1 -0
  26. package/dist/server.d.ts +3 -0
  27. package/dist/server.d.ts.map +1 -0
  28. package/dist/server.js +3 -0
  29. package/dist/server.js.map +1 -0
  30. package/dist/studio/components/asset-source.d.ts +4 -0
  31. package/dist/studio/components/asset-source.d.ts.map +1 -0
  32. package/dist/studio/components/asset-source.js +163 -0
  33. package/dist/studio/components/asset-source.js.map +1 -0
  34. package/dist/studio/components/generate-button-input.d.ts +8 -0
  35. package/dist/studio/components/generate-button-input.d.ts.map +1 -0
  36. package/dist/studio/components/generate-button-input.js +186 -0
  37. package/dist/studio/components/generate-button-input.js.map +1 -0
  38. package/dist/studio/components/input-router.d.ts +7 -0
  39. package/dist/studio/components/input-router.d.ts.map +1 -0
  40. package/dist/studio/components/input-router.js +21 -0
  41. package/dist/studio/components/input-router.js.map +1 -0
  42. package/dist/studio/files.d.ts +9 -0
  43. package/dist/studio/files.d.ts.map +1 -0
  44. package/dist/studio/files.js +86 -0
  45. package/dist/studio/files.js.map +1 -0
  46. package/dist/studio/plugin.d.ts +3 -0
  47. package/dist/studio/plugin.d.ts.map +1 -0
  48. package/dist/studio/plugin.js +47 -0
  49. package/dist/studio/plugin.js.map +1 -0
  50. package/dist/studio/settings/schema.d.ts +8 -0
  51. package/dist/studio/settings/schema.d.ts.map +1 -0
  52. package/dist/studio/settings/schema.js +110 -0
  53. package/dist/studio/settings/schema.js.map +1 -0
  54. package/dist/studio/settings/tool.d.ts +3 -0
  55. package/dist/studio/settings/tool.d.ts.map +1 -0
  56. package/dist/studio/settings/tool.js +292 -0
  57. package/dist/studio/settings/tool.js.map +1 -0
  58. package/dist/studio/settings-data.d.ts +24 -0
  59. package/dist/studio/settings-data.d.ts.map +1 -0
  60. package/dist/studio/settings-data.js +99 -0
  61. package/dist/studio/settings-data.js.map +1 -0
  62. package/dist/studio/shared-secret.d.ts +9 -0
  63. package/dist/studio/shared-secret.d.ts.map +1 -0
  64. package/dist/studio/shared-secret.js +37 -0
  65. package/dist/studio/shared-secret.js.map +1 -0
  66. package/dist/utils/config.d.ts +3 -0
  67. package/dist/utils/config.d.ts.map +1 -0
  68. package/dist/utils/config.js +41 -0
  69. package/dist/utils/config.js.map +1 -0
  70. package/dist/utils/context-fields.d.ts +16 -0
  71. package/dist/utils/context-fields.d.ts.map +1 -0
  72. package/dist/utils/context-fields.js +104 -0
  73. package/dist/utils/context-fields.js.map +1 -0
  74. package/dist/utils/document-paths.d.ts +10 -0
  75. package/dist/utils/document-paths.d.ts.map +1 -0
  76. package/dist/utils/document-paths.js +33 -0
  77. package/dist/utils/document-paths.js.map +1 -0
  78. package/dist/utils/models.d.ts +22 -0
  79. package/dist/utils/models.d.ts.map +1 -0
  80. package/dist/utils/models.js +76 -0
  81. package/dist/utils/models.js.map +1 -0
  82. package/dist/utils/prompts.d.ts +2 -0
  83. package/dist/utils/prompts.d.ts.map +1 -0
  84. package/dist/utils/prompts.js +7 -0
  85. package/dist/utils/prompts.js.map +1 -0
  86. package/dist/utils/shared.d.ts +82 -0
  87. package/dist/utils/shared.d.ts.map +1 -0
  88. package/dist/utils/shared.js +13 -0
  89. package/dist/utils/shared.js.map +1 -0
  90. package/package.json +53 -47
  91. package/src/index.ts +0 -23
  92. package/src/presets/article-featured-image.ts +0 -23
  93. package/src/server/handle-request.ts +0 -207
  94. package/src/server/types.ts +0 -30
  95. package/src/server/utils.ts +0 -395
  96. package/src/server.ts +0 -14
  97. package/src/studio/components/asset-source.tsx +0 -297
  98. package/src/studio/components/generate-button-input.tsx +0 -380
  99. package/src/studio/components/input-router.tsx +0 -41
  100. package/src/studio/files.ts +0 -114
  101. package/src/studio/plugin.tsx +0 -54
  102. package/src/studio/settings/schema.ts +0 -122
  103. package/src/studio/settings/tool.tsx +0 -587
  104. package/src/studio/settings-data.ts +0 -172
  105. package/src/utils/config.ts +0 -55
  106. package/src/utils/context-fields.ts +0 -172
  107. package/src/utils/document-paths.ts +0 -51
  108. package/src/utils/models.ts +0 -126
  109. package/src/utils/prompts.ts +0 -6
  110. package/src/utils/shared.ts +0 -88
@@ -1,380 +0,0 @@
1
- "use client"
2
-
3
- import {
4
- Box,
5
- Button,
6
- Card,
7
- Dialog,
8
- Flex,
9
- Stack,
10
- Text,
11
- TextArea,
12
- } from "@sanity/ui"
13
- import { type ChangeEvent, useMemo, useState } from "react"
14
- import {
15
- PatchEvent,
16
- type ImageValue,
17
- type ObjectInputProps,
18
- set,
19
- setIfMissing,
20
- useClient,
21
- useFormValue,
22
- useSchema,
23
- } from "sanity"
24
- import {
25
- buildContextFieldPrompt,
26
- getDefaultSelectedContextFieldPaths,
27
- getSelectableContextFields,
28
- } from "../../utils/context-fields"
29
- import {
30
- SETTINGS_DOCUMENT_ID,
31
- SETTINGS_SCHEMA_TYPE,
32
- type GenerateButtonTarget,
33
- type ResolvedOptions,
34
- } from "../../utils/shared"
35
- import { composePrompt } from "../../utils/prompts"
36
- import {
37
- buildSettingsReferenceImages,
38
- fetchSettingsDocument,
39
- findTargetSettings,
40
- getErrorMessage,
41
- resolveSettingsModel,
42
- } from "../settings-data"
43
- import { createGeneratedFile } from "../files"
44
-
45
- type GenerateResponse = {
46
- data: string
47
- mimeType: string
48
- }
49
-
50
- type GenerationPhase = "idle" | "preparing" | "generating" | "uploading" | "updating"
51
-
52
- function getGenerationPhaseLabel(phase: GenerationPhase): string {
53
- switch (phase) {
54
- case "preparing":
55
- return "Preparing..."
56
- case "generating":
57
- return "Generating..."
58
- case "uploading":
59
- return "Uploading to Sanity..."
60
- case "updating":
61
- return "Updating field..."
62
- default:
63
- return "Generate image"
64
- }
65
- }
66
-
67
- export function GenerateButtonInput({
68
- options,
69
- props,
70
- target,
71
- }: {
72
- options: ResolvedOptions
73
- props: ObjectInputProps<ImageValue>
74
- target: GenerateButtonTarget
75
- }) {
76
- const client = useClient({ apiVersion: options.apiVersion })
77
- const schema = useSchema()
78
- const documentValue = useFormValue([]) as Record<string, unknown> | undefined
79
- const documentType = useFormValue(["_type"]) as string | undefined
80
- const [isDialogOpen, setIsDialogOpen] = useState(false)
81
- const [userPrompt, setUserPrompt] = useState("")
82
- const [selectedContextFieldPaths, setSelectedContextFieldPaths] = useState<
83
- string[]
84
- >([])
85
- const [error, setError] = useState<string | null>(null)
86
- const [generationPhase, setGenerationPhase] = useState<GenerationPhase>("idle")
87
- const isGenerating = generationPhase !== "idle"
88
- const currentAssetRef = props.value?.asset?._ref || "empty"
89
-
90
- const selectableContextFields = useMemo(
91
- () =>
92
- getSelectableContextFields(
93
- documentType ? schema.get(documentType) : undefined
94
- ),
95
- [documentType, schema]
96
- )
97
- const defaultSelectedContextFieldPaths = useMemo(
98
- () =>
99
- getDefaultSelectedContextFieldPaths({
100
- selectableContextFields,
101
- suggestedContextFieldPaths: target.suggestedContextFieldPaths,
102
- }),
103
- [selectableContextFields, target.suggestedContextFieldPaths]
104
- )
105
- const runtimePrompt = buildContextFieldPrompt({
106
- contextFieldPaths: selectedContextFieldPaths,
107
- documentValue,
108
- })
109
-
110
- function toggleContextFieldPath(fieldPath: string) {
111
- setSelectedContextFieldPaths((currentFieldPaths) =>
112
- currentFieldPaths.includes(fieldPath)
113
- ? currentFieldPaths.filter(
114
- (currentFieldPath) => currentFieldPath !== fieldPath
115
- )
116
- : [...currentFieldPaths, fieldPath]
117
- )
118
- }
119
-
120
- async function handleGenerate() {
121
- setError(null)
122
- setGenerationPhase("preparing")
123
-
124
- try {
125
- const settings = await fetchSettingsDocument(
126
- client,
127
- SETTINGS_DOCUMENT_ID,
128
- SETTINGS_SCHEMA_TYPE
129
- )
130
- const targetSettings = findTargetSettings(settings, target.id)
131
- const selectedModel = resolveSettingsModel({
132
- allowedModels: options.allowedModels,
133
- settings,
134
- })
135
- const prompt = composePrompt([
136
- settings?.globalPrompt
137
- ? `Global direction:\n${settings.globalPrompt}`
138
- : null,
139
- targetSettings?.prompt
140
- ? `Target direction:\n${targetSettings.prompt}`
141
- : null,
142
- runtimePrompt ? `Document context:\n${runtimePrompt}` : null,
143
- userPrompt.trim()
144
- ? `Additional editor instructions:\n${userPrompt.trim()}`
145
- : null,
146
- ])
147
-
148
- if (!prompt) {
149
- throw new Error("Add a prompt or configure target defaults first.")
150
- }
151
-
152
- const referenceImages = await buildSettingsReferenceImages(
153
- settings,
154
- target.id
155
- )
156
- const formData = new FormData()
157
-
158
- formData.set("prompt", prompt)
159
- formData.set("model", selectedModel)
160
-
161
- for (const referenceImage of referenceImages) {
162
- formData.append("references", referenceImage)
163
- }
164
-
165
- setGenerationPhase("generating")
166
- const response = await fetch(options.apiEndpoint, {
167
- method: "POST",
168
- body: formData,
169
- })
170
- const payload = (await response.json()) as
171
- | GenerateResponse
172
- | { error?: string }
173
-
174
- if (!response.ok) {
175
- throw new Error(
176
- "error" in payload && payload.error
177
- ? payload.error
178
- : "AI image generation failed."
179
- )
180
- }
181
-
182
- if (
183
- !("data" in payload) ||
184
- typeof payload.data !== "string" ||
185
- typeof payload.mimeType !== "string"
186
- ) {
187
- throw new Error("AI Image Plugin returned an invalid image payload.")
188
- }
189
-
190
- const basename =
191
- String(
192
- documentValue?.title ||
193
- documentValue?._type ||
194
- target.title ||
195
- "generated"
196
- ).trim() || "generated"
197
- const generatedFile = createGeneratedFile(
198
- payload,
199
- basename,
200
- "ai-image-plugin-field"
201
- )
202
- setGenerationPhase("uploading")
203
- const uploadedAsset = await client.assets.upload("image", generatedFile, {
204
- filename: generatedFile.name,
205
- })
206
-
207
- setGenerationPhase("updating")
208
- props.onChange(
209
- PatchEvent.from([
210
- setIfMissing({ _type: "image" }),
211
- set({ _type: "reference", _ref: uploadedAsset._id }, ["asset"]),
212
- ])
213
- )
214
- setIsDialogOpen(false)
215
- setUserPrompt("")
216
- } catch (nextError) {
217
- setError(
218
- getErrorMessage(nextError, "Unable to generate an image for this field.")
219
- )
220
- } finally {
221
- setGenerationPhase("idle")
222
- }
223
- }
224
-
225
- return (
226
- <Stack space={4}>
227
- <Card padding={3} radius={2} tone="transparent">
228
- <Stack space={3}>
229
- <Text size={1}>
230
- {target.description ||
231
- "Generate an image with AI Image Plugin for this field."}
232
- </Text>
233
- <Flex gap={3}>
234
- <Button
235
- onClick={() => {
236
- setError(null)
237
- setSelectedContextFieldPaths(defaultSelectedContextFieldPaths)
238
- setIsDialogOpen(true)
239
- }}
240
- text="Generate"
241
- tone="primary"
242
- />
243
- </Flex>
244
- </Stack>
245
- </Card>
246
-
247
- <Box key={currentAssetRef}>{props.renderDefault(props)}</Box>
248
-
249
- {isDialogOpen ? (
250
- <Dialog
251
- header={target.dialogTitle || "Generate Image"}
252
- id={`${target.id}-generate-dialog`}
253
- onClose={() => {
254
- if (isGenerating) {
255
- return
256
- }
257
-
258
- setError(null)
259
- setIsDialogOpen(false)
260
- }}
261
- width={1}
262
- >
263
- <Box padding={4}>
264
- <Stack space={4}>
265
- <Box>
266
- <Text size={1} weight="medium">
267
- {target.promptLabel || "Custom prompt"}
268
- </Text>
269
- <Box marginTop={2}>
270
- <TextArea
271
- onChange={(event: ChangeEvent<HTMLTextAreaElement>) =>
272
- setUserPrompt(event.currentTarget.value)
273
- }
274
- placeholder={
275
- target.promptPlaceholder ||
276
- "Optional custom instructions for this image."
277
- }
278
- rows={6}
279
- value={userPrompt}
280
- />
281
- </Box>
282
- </Box>
283
-
284
- {selectableContextFields.length > 0 ? (
285
- <Box>
286
- <Stack space={3}>
287
- <Stack space={2}>
288
- <Text size={1} weight="medium">
289
- Include document fields
290
- </Text>
291
- <Flex gap={2} style={{ flexWrap: "wrap" }}>
292
- {selectableContextFields.map((field) => {
293
- const isSelected = selectedContextFieldPaths.includes(
294
- field.path
295
- )
296
-
297
- return (
298
- <Button
299
- key={field.path}
300
- mode={isSelected ? "default" : "ghost"}
301
- onClick={() => toggleContextFieldPath(field.path)}
302
- text={field.label}
303
- tone={isSelected ? "primary" : "default"}
304
- />
305
- )
306
- })}
307
- </Flex>
308
- </Stack>
309
- </Stack>
310
- </Box>
311
- ) : (
312
- <Card padding={3} radius={2} tone="transparent">
313
- <Stack space={2}>
314
- <Text size={1} weight="medium">
315
- Document context
316
- </Text>
317
- <Text muted size={1}>
318
- No supported top-level document fields are available for
319
- this type yet.
320
- </Text>
321
- </Stack>
322
- </Card>
323
- )}
324
-
325
- {selectableContextFields.length > 0 ? (
326
- <Card padding={3} radius={2} tone="transparent">
327
- <Stack space={2}>
328
- <Text size={1} weight="medium">
329
- Document context preview
330
- </Text>
331
- {runtimePrompt ? (
332
- <Text size={1}>{runtimePrompt}</Text>
333
- ) : (
334
- <Text muted size={1}>
335
- No document fields are selected with a usable value yet.
336
- </Text>
337
- )}
338
- </Stack>
339
- </Card>
340
- ) : null}
341
-
342
- {error ? (
343
- <Card padding={3} radius={2} tone="critical">
344
- <Text size={1}>{error}</Text>
345
- </Card>
346
- ) : null}
347
-
348
- {isGenerating ? (
349
- <Card padding={3} radius={2} tone="transparent">
350
- <Text size={1}>
351
- {getGenerationPhaseLabel(generationPhase)} This can take a
352
- minute or two for slower models.
353
- </Text>
354
- </Card>
355
- ) : null}
356
-
357
- <Flex gap={3} justify="flex-end">
358
- <Button
359
- disabled={isGenerating}
360
- mode="ghost"
361
- onClick={() => {
362
- setError(null)
363
- setIsDialogOpen(false)
364
- }}
365
- text="Cancel"
366
- />
367
- <Button
368
- disabled={isGenerating}
369
- onClick={() => void handleGenerate()}
370
- text={getGenerationPhaseLabel(generationPhase)}
371
- tone="primary"
372
- />
373
- </Flex>
374
- </Stack>
375
- </Box>
376
- </Dialog>
377
- ) : null}
378
- </Stack>
379
- )
380
- }
@@ -1,41 +0,0 @@
1
- "use client"
2
-
3
- import { useFormValue, type InputProps, type ObjectInputProps } from "sanity"
4
- import type { ImageValue } from "sanity"
5
- import { GenerateButtonInput } from "./generate-button-input"
6
- import { doesTargetMatchField } from "../../utils/document-paths"
7
- import type { ResolvedOptions } from "../../utils/shared"
8
-
9
- export function InputRouter({
10
- options,
11
- props,
12
- }: {
13
- options: ResolvedOptions
14
- props: InputProps
15
- }) {
16
- const documentType = useFormValue(["_type"]) as string | undefined
17
-
18
- if (props.schemaType?.name !== "image") {
19
- return props.renderDefault(props)
20
- }
21
-
22
- const target = options.targets.find((candidate) =>
23
- doesTargetMatchField({
24
- documentType,
25
- path: props.path,
26
- target: candidate,
27
- })
28
- )
29
-
30
- if (!target) {
31
- return props.renderDefault(props)
32
- }
33
-
34
- return (
35
- <GenerateButtonInput
36
- options={options}
37
- props={props as ObjectInputProps<ImageValue>}
38
- target={target}
39
- />
40
- )
41
- }
@@ -1,114 +0,0 @@
1
- "use client"
2
-
3
- function getPngFilename(filename: string): string {
4
- const basename = filename.replace(/\.[^/.]+$/, "")
5
- return `${basename || "reference"}.png`
6
- }
7
-
8
- export async function convertImageFileToPng(file: File): Promise<File> {
9
- if (file.type === "image/png") {
10
- return file
11
- }
12
-
13
- const imageUrl = URL.createObjectURL(file)
14
-
15
- try {
16
- const image = await new Promise<HTMLImageElement>((resolve, reject) => {
17
- const nextImage = new Image()
18
- nextImage.onload = () => resolve(nextImage)
19
- nextImage.onerror = () =>
20
- reject(new Error(`Unable to decode ${file.name} as an image.`))
21
- nextImage.src = imageUrl
22
- })
23
- const canvas = document.createElement("canvas")
24
- const width = image.naturalWidth || image.width
25
- const height = image.naturalHeight || image.height
26
-
27
- canvas.width = width
28
- canvas.height = height
29
-
30
- const context = canvas.getContext("2d")
31
-
32
- if (!context) {
33
- throw new Error("Unable to convert the reference image to PNG.")
34
- }
35
-
36
- context.drawImage(image, 0, 0, width, height)
37
-
38
- const blob = await new Promise<Blob>((resolve, reject) => {
39
- canvas.toBlob(
40
- (nextBlob) => {
41
- if (nextBlob) {
42
- resolve(nextBlob)
43
- return
44
- }
45
-
46
- reject(new Error("Unable to convert the reference image to PNG."))
47
- },
48
- "image/png",
49
- 1
50
- )
51
- })
52
-
53
- return new File([blob], getPngFilename(file.name), { type: "image/png" })
54
- } finally {
55
- URL.revokeObjectURL(imageUrl)
56
- }
57
- }
58
-
59
- export function base64ToBlob(base64: string, mimeType: string): Blob {
60
- const binaryString = atob(base64)
61
- const bytes = new Uint8Array(binaryString.length)
62
-
63
- for (const [index, character] of Array.from(binaryString).entries()) {
64
- bytes[index] = character.charCodeAt(0)
65
- }
66
-
67
- return new Blob([bytes], { type: mimeType })
68
- }
69
-
70
- export function fileExtensionFromMimeType(mimeType: string): string {
71
- switch (mimeType) {
72
- case "image/jpeg":
73
- return "jpg"
74
- case "image/webp":
75
- return "webp"
76
- default:
77
- return "png"
78
- }
79
- }
80
-
81
- export function formatFileSize(bytes: number): string {
82
- if (bytes < 1024) {
83
- return `${bytes} B`
84
- }
85
-
86
- const kilobytes = bytes / 1024
87
-
88
- if (kilobytes < 1024) {
89
- return `${kilobytes.toFixed(1)} KB`
90
- }
91
-
92
- return `${(kilobytes / 1024).toFixed(1)} MB`
93
- }
94
-
95
- export function createGeneratedFile(
96
- result: { data: string; mimeType: string },
97
- basename: string,
98
- prefix = "ai-image-plugin"
99
- ): File {
100
- const extension = fileExtensionFromMimeType(result.mimeType)
101
- const timestamp = new Date().toISOString().replaceAll(/[:.]/g, "-")
102
- const safeBasename = basename
103
- .trim()
104
- .toLowerCase()
105
- .replaceAll(/[^a-z0-9]+/g, "-")
106
- .replaceAll(/^-+|-+$/g, "")
107
- .slice(0, 40)
108
- const suffix = safeBasename || "generated"
109
- const blob = base64ToBlob(result.data, result.mimeType)
110
-
111
- return new File([blob], `${prefix}-${suffix}-${timestamp}.${extension}`, {
112
- type: result.mimeType,
113
- })
114
- }
@@ -1,54 +0,0 @@
1
- import { definePlugin, type AssetSource, type InputProps } from "sanity"
2
- import { createAssetSource } from "./components/asset-source"
3
- import { InputRouter } from "./components/input-router"
4
- import { createSettingsSchema } from "./settings/schema"
5
- import { createSettingsTool } from "./settings/tool"
6
- import { normalizeOptions } from "../utils/config"
7
- import type { PluginOptions } from "../utils/shared"
8
-
9
- export const aiImagePlugin = definePlugin<PluginOptions>((rawOptions) => {
10
- const options = normalizeOptions(rawOptions)
11
- const assetSource = options.assetSourceTarget
12
- ? createAssetSource(options)
13
- : null
14
- const settingsTool = createSettingsTool(options)
15
- const settingsSchema = createSettingsSchema(options)
16
-
17
- return {
18
- name: "ai-image-plugin",
19
- ...(options.registerSettingsSchemaType
20
- ? {
21
- schema: {
22
- types: [settingsSchema],
23
- },
24
- }
25
- : {}),
26
- tools: [
27
- {
28
- name: options.settingsToolName,
29
- title: options.settingsToolTitle,
30
- component: settingsTool,
31
- },
32
- ],
33
- form: {
34
- ...(assetSource
35
- ? {
36
- image: {
37
- assetSources: (previousAssetSources: AssetSource[]) =>
38
- previousAssetSources.some(
39
- (previousAssetSource) =>
40
- previousAssetSource.name === assetSource.name
41
- )
42
- ? previousAssetSources
43
- : [...previousAssetSources, assetSource],
44
- },
45
- }
46
- : {}),
47
- components: {
48
- input: (props: InputProps) => (
49
- <InputRouter options={options} props={props} />
50
- ),
51
- },
52
- },
53
- }
54
- })
@@ -1,122 +0,0 @@
1
- import { defineArrayMember, defineField, defineType } from "sanity"
2
- import {
3
- getDefaultAllowedAiImageModel,
4
- getSupportedAiImageModelOptions,
5
- } from "../../utils/models"
6
- import {
7
- SETTINGS_DOCUMENT_ID,
8
- SETTINGS_SCHEMA_TYPE,
9
- type ResolvedOptions,
10
- } from "../../utils/shared"
11
-
12
- export function createSettingsSchema(options: ResolvedOptions) {
13
- return defineType({
14
- name: SETTINGS_SCHEMA_TYPE,
15
- title: "AI Image Plugin Settings",
16
- type: "document",
17
- fields: [
18
- defineField({
19
- name: "title",
20
- title: "Title",
21
- type: "string",
22
- initialValue: "AI Image Plugin Settings",
23
- readOnly: true,
24
- }),
25
- defineField({
26
- name: "globalPrompt",
27
- title: "Global Prompt",
28
- type: "text",
29
- rows: 8,
30
- description:
31
- "Prompt instructions automatically included in every AI Image Plugin generation.",
32
- }),
33
- defineField({
34
- name: "globalModel",
35
- title: "Default Model",
36
- type: "string",
37
- initialValue: getDefaultAllowedAiImageModel(options.allowedModels),
38
- description:
39
- "The AI image model used by the plugin unless a different allowed default is configured later.",
40
- options: {
41
- list: getSupportedAiImageModelOptions(options.allowedModels),
42
- },
43
- }),
44
- defineField({
45
- name: "globalReferenceImages",
46
- title: "Global Reference Images",
47
- type: "array",
48
- description:
49
- "Reference images automatically included in every AI Image Plugin generation.",
50
- of: [
51
- defineArrayMember({
52
- type: "image",
53
- options: { hotspot: true },
54
- }),
55
- ],
56
- }),
57
- defineField({
58
- name: "targetConfigs",
59
- title: "Target Overrides",
60
- type: "array",
61
- description:
62
- "Per-target prompt and reference images for specific AI Image Plugin entry points.",
63
- of: [
64
- defineArrayMember({
65
- type: "object",
66
- fields: [
67
- defineField({
68
- name: "targetId",
69
- title: "Target ID",
70
- type: "string",
71
- validation: (Rule) => Rule.required(),
72
- options: {
73
- list: [
74
- ...(options.assetSourceTarget
75
- ? [
76
- {
77
- title:
78
- options.assetSourceTarget.title ||
79
- options.assetSourceTarget.id,
80
- value: options.assetSourceTarget.id,
81
- },
82
- ]
83
- : []),
84
- ...options.targets.map((target) => ({
85
- title: target.title || target.id,
86
- value: target.id,
87
- })),
88
- ],
89
- },
90
- }),
91
- defineField({
92
- name: "prompt",
93
- title: "Prompt",
94
- type: "text",
95
- rows: 6,
96
- }),
97
- defineField({
98
- name: "referenceImages",
99
- title: "Reference Images",
100
- type: "array",
101
- of: [
102
- defineArrayMember({
103
- type: "image",
104
- options: { hotspot: true },
105
- }),
106
- ],
107
- }),
108
- ],
109
- }),
110
- ],
111
- }),
112
- ],
113
- preview: {
114
- prepare() {
115
- return {
116
- title: "AI Image Plugin Settings",
117
- subtitle: SETTINGS_DOCUMENT_ID,
118
- }
119
- },
120
- },
121
- })
122
- }