@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.
- package/README.md +281 -257
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/presets/article-featured-image.d.ts +3 -0
- package/dist/presets/article-featured-image.d.ts.map +1 -0
- package/dist/presets/article-featured-image.js +17 -0
- package/dist/presets/article-featured-image.js.map +1 -0
- package/dist/server/constants.d.ts +6 -0
- package/dist/server/constants.d.ts.map +1 -0
- package/{src/server/constants.ts → dist/server/constants.js} +17 -20
- package/dist/server/constants.js.map +1 -0
- package/dist/server/handle-request.d.ts +5 -0
- package/dist/server/handle-request.d.ts.map +1 -0
- package/dist/server/handle-request.js +122 -0
- package/dist/server/handle-request.js.map +1 -0
- package/dist/server/types.d.ts +28 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/utils.d.ts +50 -0
- package/dist/server/utils.d.ts.map +1 -0
- package/dist/server/utils.js +274 -0
- package/dist/server/utils.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +3 -0
- package/dist/server.js.map +1 -0
- package/dist/studio/components/asset-source.d.ts +4 -0
- package/dist/studio/components/asset-source.d.ts.map +1 -0
- package/dist/studio/components/asset-source.js +163 -0
- package/dist/studio/components/asset-source.js.map +1 -0
- package/dist/studio/components/generate-button-input.d.ts +8 -0
- package/dist/studio/components/generate-button-input.d.ts.map +1 -0
- package/dist/studio/components/generate-button-input.js +186 -0
- package/dist/studio/components/generate-button-input.js.map +1 -0
- package/dist/studio/components/input-router.d.ts +7 -0
- package/dist/studio/components/input-router.d.ts.map +1 -0
- package/dist/studio/components/input-router.js +21 -0
- package/dist/studio/components/input-router.js.map +1 -0
- package/dist/studio/files.d.ts +9 -0
- package/dist/studio/files.d.ts.map +1 -0
- package/dist/studio/files.js +86 -0
- package/dist/studio/files.js.map +1 -0
- package/dist/studio/plugin.d.ts +3 -0
- package/dist/studio/plugin.d.ts.map +1 -0
- package/dist/studio/plugin.js +47 -0
- package/dist/studio/plugin.js.map +1 -0
- package/dist/studio/settings/schema.d.ts +8 -0
- package/dist/studio/settings/schema.d.ts.map +1 -0
- package/dist/studio/settings/schema.js +110 -0
- package/dist/studio/settings/schema.js.map +1 -0
- package/dist/studio/settings/tool.d.ts +3 -0
- package/dist/studio/settings/tool.d.ts.map +1 -0
- package/dist/studio/settings/tool.js +292 -0
- package/dist/studio/settings/tool.js.map +1 -0
- package/dist/studio/settings-data.d.ts +24 -0
- package/dist/studio/settings-data.d.ts.map +1 -0
- package/dist/studio/settings-data.js +99 -0
- package/dist/studio/settings-data.js.map +1 -0
- package/dist/studio/shared-secret.d.ts +9 -0
- package/dist/studio/shared-secret.d.ts.map +1 -0
- package/dist/studio/shared-secret.js +37 -0
- package/dist/studio/shared-secret.js.map +1 -0
- package/dist/utils/config.d.ts +3 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +41 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/context-fields.d.ts +16 -0
- package/dist/utils/context-fields.d.ts.map +1 -0
- package/dist/utils/context-fields.js +104 -0
- package/dist/utils/context-fields.js.map +1 -0
- package/dist/utils/document-paths.d.ts +10 -0
- package/dist/utils/document-paths.d.ts.map +1 -0
- package/dist/utils/document-paths.js +33 -0
- package/dist/utils/document-paths.js.map +1 -0
- package/dist/utils/models.d.ts +22 -0
- package/dist/utils/models.d.ts.map +1 -0
- package/dist/utils/models.js +76 -0
- package/dist/utils/models.js.map +1 -0
- package/dist/utils/prompts.d.ts +2 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +7 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/shared.d.ts +82 -0
- package/dist/utils/shared.d.ts.map +1 -0
- package/dist/utils/shared.js +13 -0
- package/dist/utils/shared.js.map +1 -0
- package/package.json +53 -47
- package/src/index.ts +0 -23
- package/src/presets/article-featured-image.ts +0 -23
- package/src/server/handle-request.ts +0 -207
- package/src/server/types.ts +0 -30
- package/src/server/utils.ts +0 -395
- package/src/server.ts +0 -14
- package/src/studio/components/asset-source.tsx +0 -297
- package/src/studio/components/generate-button-input.tsx +0 -380
- package/src/studio/components/input-router.tsx +0 -41
- package/src/studio/files.ts +0 -114
- package/src/studio/plugin.tsx +0 -54
- package/src/studio/settings/schema.ts +0 -122
- package/src/studio/settings/tool.tsx +0 -587
- package/src/studio/settings-data.ts +0 -172
- package/src/utils/config.ts +0 -55
- package/src/utils/context-fields.ts +0 -172
- package/src/utils/document-paths.ts +0 -51
- package/src/utils/models.ts +0 -126
- package/src/utils/prompts.ts +0 -6
- package/src/utils/shared.ts +0 -88
|
@@ -1,587 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Box,
|
|
5
|
-
Button,
|
|
6
|
-
Card,
|
|
7
|
-
Flex,
|
|
8
|
-
Grid,
|
|
9
|
-
Select,
|
|
10
|
-
Stack,
|
|
11
|
-
Text,
|
|
12
|
-
TextArea,
|
|
13
|
-
} from "@sanity/ui"
|
|
14
|
-
import { type ChangeEvent, useEffect, useMemo, useState } from "react"
|
|
15
|
-
import { useClient } from "sanity"
|
|
16
|
-
import {
|
|
17
|
-
getDefaultAllowedAiImageModel,
|
|
18
|
-
getSupportedAiImageModelOptions,
|
|
19
|
-
type SupportedAiImageModelId,
|
|
20
|
-
} from "../../utils/models"
|
|
21
|
-
import {
|
|
22
|
-
MAX_REFERENCE_IMAGES,
|
|
23
|
-
SETTINGS_DOCUMENT_ID,
|
|
24
|
-
SETTINGS_SCHEMA_TYPE,
|
|
25
|
-
type ResolvedOptions,
|
|
26
|
-
} from "../../utils/shared"
|
|
27
|
-
import {
|
|
28
|
-
appendReferenceImages,
|
|
29
|
-
createImageReferenceValue,
|
|
30
|
-
createToolReferenceImageFromStoredImage,
|
|
31
|
-
fetchSettingsDocument,
|
|
32
|
-
getErrorMessage,
|
|
33
|
-
resolveSettingsModel,
|
|
34
|
-
type ToolReferenceImage,
|
|
35
|
-
} from "../settings-data"
|
|
36
|
-
import { convertImageFileToPng, formatFileSize } from "../files"
|
|
37
|
-
|
|
38
|
-
type TargetEditorState = {
|
|
39
|
-
prompt: string
|
|
40
|
-
referenceImages: ToolReferenceImage[]
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function SettingsReferenceImageCard({
|
|
44
|
-
image,
|
|
45
|
-
onRemove,
|
|
46
|
-
}: {
|
|
47
|
-
image: ToolReferenceImage
|
|
48
|
-
onRemove: (imageId: string) => void
|
|
49
|
-
}) {
|
|
50
|
-
const [localPreviewUrl, setLocalPreviewUrl] = useState<string | null>(null)
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
if (image.previewUrl || !image.sourceFile) {
|
|
54
|
-
setLocalPreviewUrl(null)
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const objectUrl = URL.createObjectURL(image.sourceFile)
|
|
59
|
-
setLocalPreviewUrl(objectUrl)
|
|
60
|
-
|
|
61
|
-
return () => {
|
|
62
|
-
URL.revokeObjectURL(objectUrl)
|
|
63
|
-
}
|
|
64
|
-
}, [image.previewUrl, image.sourceFile])
|
|
65
|
-
|
|
66
|
-
const previewUrl = image.previewUrl || localPreviewUrl
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<Card padding={3} radius={2} tone="transparent">
|
|
70
|
-
<Flex align="center" gap={3} justify="space-between">
|
|
71
|
-
<Flex align="center" flex={1} gap={3}>
|
|
72
|
-
<Card
|
|
73
|
-
overflow="hidden"
|
|
74
|
-
radius={2}
|
|
75
|
-
style={{ flexShrink: 0, height: 128, width: 128 }}
|
|
76
|
-
tone="default"
|
|
77
|
-
>
|
|
78
|
-
{previewUrl ? (
|
|
79
|
-
<img
|
|
80
|
-
alt={image.name}
|
|
81
|
-
src={previewUrl}
|
|
82
|
-
style={{
|
|
83
|
-
display: "block",
|
|
84
|
-
height: "100%",
|
|
85
|
-
objectFit: "cover",
|
|
86
|
-
width: "100%",
|
|
87
|
-
}}
|
|
88
|
-
/>
|
|
89
|
-
) : (
|
|
90
|
-
<Flex align="center" justify="center" style={{ height: "100%" }}>
|
|
91
|
-
<Text muted size={1}>
|
|
92
|
-
No preview
|
|
93
|
-
</Text>
|
|
94
|
-
</Flex>
|
|
95
|
-
)}
|
|
96
|
-
</Card>
|
|
97
|
-
|
|
98
|
-
<Box flex={1}>
|
|
99
|
-
<Text size={1} weight="medium">
|
|
100
|
-
{image.name}
|
|
101
|
-
</Text>
|
|
102
|
-
<Text muted size={1}>
|
|
103
|
-
{image.sourceFile
|
|
104
|
-
? formatFileSize(image.sourceFile.size)
|
|
105
|
-
: "Stored in Sanity"}
|
|
106
|
-
</Text>
|
|
107
|
-
</Box>
|
|
108
|
-
</Flex>
|
|
109
|
-
|
|
110
|
-
<Button mode="bleed" onClick={() => onRemove(image.id)} text="Remove" />
|
|
111
|
-
</Flex>
|
|
112
|
-
</Card>
|
|
113
|
-
)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function buildDefaultTargetState(
|
|
117
|
-
options: ResolvedOptions
|
|
118
|
-
): Record<string, TargetEditorState> {
|
|
119
|
-
return Object.fromEntries(
|
|
120
|
-
[
|
|
121
|
-
...(options.assetSourceTarget ? [options.assetSourceTarget] : []),
|
|
122
|
-
...options.targets,
|
|
123
|
-
].map((target) => [
|
|
124
|
-
target.id,
|
|
125
|
-
{
|
|
126
|
-
prompt: "",
|
|
127
|
-
referenceImages: [],
|
|
128
|
-
},
|
|
129
|
-
])
|
|
130
|
-
)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function SettingsReferenceImagesEditor({
|
|
134
|
-
images,
|
|
135
|
-
label,
|
|
136
|
-
onAdd,
|
|
137
|
-
onRemove,
|
|
138
|
-
}: {
|
|
139
|
-
images: ToolReferenceImage[]
|
|
140
|
-
label: string
|
|
141
|
-
onAdd: (files: File[]) => void
|
|
142
|
-
onRemove: (imageId: string) => void
|
|
143
|
-
}) {
|
|
144
|
-
return (
|
|
145
|
-
<Stack space={3}>
|
|
146
|
-
<Stack space={1}>
|
|
147
|
-
<Text size={1} weight="medium">
|
|
148
|
-
{label}
|
|
149
|
-
</Text>
|
|
150
|
-
<Text muted size={1}>
|
|
151
|
-
Up to {MAX_REFERENCE_IMAGES} images.
|
|
152
|
-
</Text>
|
|
153
|
-
</Stack>
|
|
154
|
-
<input
|
|
155
|
-
accept="image/*"
|
|
156
|
-
multiple
|
|
157
|
-
onChange={(event) => {
|
|
158
|
-
const files = Array.from(event.currentTarget.files || [])
|
|
159
|
-
|
|
160
|
-
if (files.length > 0) {
|
|
161
|
-
onAdd(files)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
event.currentTarget.value = ""
|
|
165
|
-
}}
|
|
166
|
-
type="file"
|
|
167
|
-
/>
|
|
168
|
-
|
|
169
|
-
{images.length > 0 ? (
|
|
170
|
-
<Stack space={2}>
|
|
171
|
-
{images.map((image) => (
|
|
172
|
-
<SettingsReferenceImageCard
|
|
173
|
-
image={image}
|
|
174
|
-
key={image.id}
|
|
175
|
-
onRemove={onRemove}
|
|
176
|
-
/>
|
|
177
|
-
))}
|
|
178
|
-
</Stack>
|
|
179
|
-
) : (
|
|
180
|
-
<Text muted size={1}>
|
|
181
|
-
No reference images yet.
|
|
182
|
-
</Text>
|
|
183
|
-
)}
|
|
184
|
-
</Stack>
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function createSettingsTool(options: ResolvedOptions) {
|
|
189
|
-
return function SettingsTool() {
|
|
190
|
-
const client = useClient({ apiVersion: options.apiVersion })
|
|
191
|
-
const [isLoading, setIsLoading] = useState(true)
|
|
192
|
-
const [isSaving, setIsSaving] = useState(false)
|
|
193
|
-
const [saveMessage, setSaveMessage] = useState<string | null>(null)
|
|
194
|
-
const [error, setError] = useState<string | null>(null)
|
|
195
|
-
const [globalModel, setGlobalModel] = useState<SupportedAiImageModelId>(
|
|
196
|
-
getDefaultAllowedAiImageModel(options.allowedModels)
|
|
197
|
-
)
|
|
198
|
-
const [globalPrompt, setGlobalPrompt] = useState("")
|
|
199
|
-
const [globalReferenceImages, setGlobalReferenceImages] = useState<
|
|
200
|
-
ToolReferenceImage[]
|
|
201
|
-
>([])
|
|
202
|
-
const [targetState, setTargetState] = useState<
|
|
203
|
-
Record<string, TargetEditorState>
|
|
204
|
-
>(() => buildDefaultTargetState(options))
|
|
205
|
-
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
let isMounted = true
|
|
208
|
-
|
|
209
|
-
async function loadSettings() {
|
|
210
|
-
try {
|
|
211
|
-
const settings = await fetchSettingsDocument(
|
|
212
|
-
client,
|
|
213
|
-
SETTINGS_DOCUMENT_ID,
|
|
214
|
-
SETTINGS_SCHEMA_TYPE
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
if (!isMounted) {
|
|
218
|
-
return
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
setGlobalModel(
|
|
222
|
-
resolveSettingsModel({
|
|
223
|
-
allowedModels: options.allowedModels,
|
|
224
|
-
settings,
|
|
225
|
-
})
|
|
226
|
-
)
|
|
227
|
-
setGlobalPrompt(settings?.globalPrompt || "")
|
|
228
|
-
setGlobalReferenceImages(
|
|
229
|
-
(settings?.globalReferenceImages || [])
|
|
230
|
-
.map(createToolReferenceImageFromStoredImage)
|
|
231
|
-
.filter((image): image is ToolReferenceImage => image !== null)
|
|
232
|
-
)
|
|
233
|
-
setTargetState((currentState) => {
|
|
234
|
-
const nextState = buildDefaultTargetState(options)
|
|
235
|
-
|
|
236
|
-
for (const [targetId, state] of Object.entries(currentState)) {
|
|
237
|
-
nextState[targetId] = state
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
for (const targetConfig of settings?.targetConfigs || []) {
|
|
241
|
-
if (!targetConfig.targetId || !nextState[targetConfig.targetId]) {
|
|
242
|
-
continue
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
nextState[targetConfig.targetId] = {
|
|
246
|
-
prompt: targetConfig.prompt || "",
|
|
247
|
-
referenceImages: (targetConfig.referenceImages || [])
|
|
248
|
-
.map(createToolReferenceImageFromStoredImage)
|
|
249
|
-
.filter((image): image is ToolReferenceImage => image !== null),
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return nextState
|
|
254
|
-
})
|
|
255
|
-
} catch (nextError) {
|
|
256
|
-
if (!isMounted) {
|
|
257
|
-
return
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
setError(
|
|
261
|
-
getErrorMessage(nextError, "Unable to load AI Image Plugin settings.")
|
|
262
|
-
)
|
|
263
|
-
} finally {
|
|
264
|
-
if (isMounted) {
|
|
265
|
-
setIsLoading(false)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
void loadSettings()
|
|
271
|
-
|
|
272
|
-
return () => {
|
|
273
|
-
isMounted = false
|
|
274
|
-
}
|
|
275
|
-
}, [client, options])
|
|
276
|
-
|
|
277
|
-
const allTargets = useMemo(
|
|
278
|
-
() => [
|
|
279
|
-
...(options.assetSourceTarget ? [options.assetSourceTarget] : []),
|
|
280
|
-
...options.targets,
|
|
281
|
-
],
|
|
282
|
-
[options]
|
|
283
|
-
)
|
|
284
|
-
const allowedModelOptions = useMemo(
|
|
285
|
-
() => getSupportedAiImageModelOptions(options.allowedModels),
|
|
286
|
-
[options.allowedModels]
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
async function uploadReferenceImages(
|
|
290
|
-
images: ToolReferenceImage[]
|
|
291
|
-
): Promise<ToolReferenceImage[]> {
|
|
292
|
-
return Promise.all(
|
|
293
|
-
images.map(async (image) => {
|
|
294
|
-
if (image.assetId || !image.sourceFile) {
|
|
295
|
-
return image
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const normalizedFile = await convertImageFileToPng(image.sourceFile)
|
|
299
|
-
const uploadedAsset = await client.assets.upload("image", normalizedFile, {
|
|
300
|
-
filename: normalizedFile.name,
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
id: uploadedAsset._id,
|
|
305
|
-
assetId: uploadedAsset._id,
|
|
306
|
-
name: uploadedAsset.originalFilename || normalizedFile.name,
|
|
307
|
-
previewUrl: uploadedAsset.url,
|
|
308
|
-
}
|
|
309
|
-
})
|
|
310
|
-
)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async function handleSave() {
|
|
314
|
-
setError(null)
|
|
315
|
-
setSaveMessage(null)
|
|
316
|
-
setIsSaving(true)
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
const uploadedGlobalReferenceImages = await uploadReferenceImages(
|
|
320
|
-
globalReferenceImages
|
|
321
|
-
)
|
|
322
|
-
const uploadedTargets = await Promise.all(
|
|
323
|
-
allTargets.map(async (target) => {
|
|
324
|
-
const currentTargetState = targetState[target.id] || {
|
|
325
|
-
prompt: "",
|
|
326
|
-
referenceImages: [],
|
|
327
|
-
}
|
|
328
|
-
const uploadedReferenceImages = await uploadReferenceImages(
|
|
329
|
-
currentTargetState.referenceImages
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
return {
|
|
333
|
-
targetId: target.id,
|
|
334
|
-
prompt: currentTargetState.prompt.trim() || undefined,
|
|
335
|
-
referenceImages: uploadedReferenceImages
|
|
336
|
-
.filter((image) => image.assetId)
|
|
337
|
-
.map((image) => createImageReferenceValue(image.assetId!)),
|
|
338
|
-
uploadedReferenceImages,
|
|
339
|
-
}
|
|
340
|
-
})
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
await client.createOrReplace({
|
|
344
|
-
_id: SETTINGS_DOCUMENT_ID,
|
|
345
|
-
_type: SETTINGS_SCHEMA_TYPE,
|
|
346
|
-
title: "AI Image Plugin Settings",
|
|
347
|
-
globalModel,
|
|
348
|
-
globalPrompt: globalPrompt.trim() || undefined,
|
|
349
|
-
globalReferenceImages: uploadedGlobalReferenceImages
|
|
350
|
-
.filter((image) => image.assetId)
|
|
351
|
-
.map((image) => createImageReferenceValue(image.assetId!)),
|
|
352
|
-
targetConfigs: uploadedTargets.map((target) => ({
|
|
353
|
-
_type: "object",
|
|
354
|
-
targetId: target.targetId,
|
|
355
|
-
prompt: target.prompt,
|
|
356
|
-
referenceImages: target.referenceImages,
|
|
357
|
-
})),
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
setGlobalReferenceImages(uploadedGlobalReferenceImages)
|
|
361
|
-
setTargetState((currentState) =>
|
|
362
|
-
Object.fromEntries(
|
|
363
|
-
Object.entries(currentState).map(([targetId, state]) => {
|
|
364
|
-
const uploadedTarget = uploadedTargets.find(
|
|
365
|
-
(target) => target.targetId === targetId
|
|
366
|
-
)
|
|
367
|
-
|
|
368
|
-
if (!uploadedTarget) {
|
|
369
|
-
return [targetId, state]
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return [
|
|
373
|
-
targetId,
|
|
374
|
-
{
|
|
375
|
-
...state,
|
|
376
|
-
referenceImages: uploadedTarget.uploadedReferenceImages,
|
|
377
|
-
},
|
|
378
|
-
]
|
|
379
|
-
})
|
|
380
|
-
)
|
|
381
|
-
)
|
|
382
|
-
setSaveMessage("Saved AI Image Plugin settings.")
|
|
383
|
-
} catch (nextError) {
|
|
384
|
-
setError(
|
|
385
|
-
getErrorMessage(nextError, "Unable to save AI Image Plugin settings.")
|
|
386
|
-
)
|
|
387
|
-
} finally {
|
|
388
|
-
setIsSaving(false)
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (isLoading) {
|
|
393
|
-
return (
|
|
394
|
-
<Box padding={4}>
|
|
395
|
-
<Text>Loading AI Image Plugin settings...</Text>
|
|
396
|
-
</Box>
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return (
|
|
401
|
-
<Box padding={4}>
|
|
402
|
-
<Stack space={4}>
|
|
403
|
-
<Card padding={4} radius={2} tone="primary">
|
|
404
|
-
<Stack space={3}>
|
|
405
|
-
<Text size={3} weight="semibold">
|
|
406
|
-
AI Image Plugin
|
|
407
|
-
</Text>
|
|
408
|
-
<Text size={1}>
|
|
409
|
-
Configure reusable prompts and reference images for every AI image
|
|
410
|
-
generation entry point without wiring extra schema or structure
|
|
411
|
-
files into each project.
|
|
412
|
-
</Text>
|
|
413
|
-
</Stack>
|
|
414
|
-
</Card>
|
|
415
|
-
|
|
416
|
-
<Card padding={4} radius={2}>
|
|
417
|
-
<Stack space={4}>
|
|
418
|
-
<Text size={2} weight="semibold">
|
|
419
|
-
Global Defaults
|
|
420
|
-
</Text>
|
|
421
|
-
|
|
422
|
-
<Box>
|
|
423
|
-
<Text size={1} weight="medium">
|
|
424
|
-
Default model
|
|
425
|
-
</Text>
|
|
426
|
-
<Box marginTop={2}>
|
|
427
|
-
<Select
|
|
428
|
-
onChange={(event) =>
|
|
429
|
-
setGlobalModel(
|
|
430
|
-
event.currentTarget.value as SupportedAiImageModelId
|
|
431
|
-
)
|
|
432
|
-
}
|
|
433
|
-
value={globalModel}
|
|
434
|
-
>
|
|
435
|
-
{allowedModelOptions.map((modelOption) => (
|
|
436
|
-
<option key={modelOption.value} value={modelOption.value}>
|
|
437
|
-
{modelOption.title}
|
|
438
|
-
</option>
|
|
439
|
-
))}
|
|
440
|
-
</Select>
|
|
441
|
-
</Box>
|
|
442
|
-
</Box>
|
|
443
|
-
|
|
444
|
-
<Box>
|
|
445
|
-
<Text size={1} weight="medium">
|
|
446
|
-
Global prompt
|
|
447
|
-
</Text>
|
|
448
|
-
<Box marginTop={2}>
|
|
449
|
-
<TextArea
|
|
450
|
-
onChange={(event: ChangeEvent<HTMLTextAreaElement>) =>
|
|
451
|
-
setGlobalPrompt(event.currentTarget.value)
|
|
452
|
-
}
|
|
453
|
-
placeholder="High-level prompt instructions that should apply to every generation."
|
|
454
|
-
rows={8}
|
|
455
|
-
value={globalPrompt}
|
|
456
|
-
/>
|
|
457
|
-
</Box>
|
|
458
|
-
</Box>
|
|
459
|
-
|
|
460
|
-
<SettingsReferenceImagesEditor
|
|
461
|
-
images={globalReferenceImages}
|
|
462
|
-
label="Global reference images"
|
|
463
|
-
onAdd={(files) =>
|
|
464
|
-
setGlobalReferenceImages((currentImages) =>
|
|
465
|
-
appendReferenceImages(currentImages, files)
|
|
466
|
-
)
|
|
467
|
-
}
|
|
468
|
-
onRemove={(imageId) =>
|
|
469
|
-
setGlobalReferenceImages((currentImages) =>
|
|
470
|
-
currentImages.filter((image) => image.id !== imageId)
|
|
471
|
-
)
|
|
472
|
-
}
|
|
473
|
-
/>
|
|
474
|
-
</Stack>
|
|
475
|
-
</Card>
|
|
476
|
-
|
|
477
|
-
<Grid columns={[1, 1, 2]} gap={4}>
|
|
478
|
-
{allTargets.map((target) => {
|
|
479
|
-
const state = targetState[target.id] || {
|
|
480
|
-
prompt: "",
|
|
481
|
-
referenceImages: [],
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
return (
|
|
485
|
-
<Card key={target.id} padding={4} radius={2}>
|
|
486
|
-
<Stack space={4}>
|
|
487
|
-
<Stack space={2}>
|
|
488
|
-
<Text size={2} weight="semibold">
|
|
489
|
-
{target.title || target.id}
|
|
490
|
-
</Text>
|
|
491
|
-
<Text muted size={1}>
|
|
492
|
-
{"fieldPath" in target
|
|
493
|
-
? `${target.documentType}.${target.fieldPath}`
|
|
494
|
-
: "Shared asset picker configuration"}
|
|
495
|
-
</Text>
|
|
496
|
-
{"fieldPath" in target &&
|
|
497
|
-
target.suggestedContextFieldPaths?.length ? (
|
|
498
|
-
<Text muted size={1}>
|
|
499
|
-
Suggested tags:{" "}
|
|
500
|
-
{target.suggestedContextFieldPaths.join(", ")}
|
|
501
|
-
</Text>
|
|
502
|
-
) : null}
|
|
503
|
-
{target.description ? (
|
|
504
|
-
<Text size={1}>{target.description}</Text>
|
|
505
|
-
) : null}
|
|
506
|
-
</Stack>
|
|
507
|
-
|
|
508
|
-
<Box>
|
|
509
|
-
<Text size={1} weight="medium">
|
|
510
|
-
Target prompt
|
|
511
|
-
</Text>
|
|
512
|
-
<Box marginTop={2}>
|
|
513
|
-
<TextArea
|
|
514
|
-
onChange={(event: ChangeEvent<HTMLTextAreaElement>) =>
|
|
515
|
-
setTargetState((currentState) => ({
|
|
516
|
-
...currentState,
|
|
517
|
-
[target.id]: {
|
|
518
|
-
...state,
|
|
519
|
-
prompt: event.currentTarget.value,
|
|
520
|
-
},
|
|
521
|
-
}))
|
|
522
|
-
}
|
|
523
|
-
placeholder="Optional prompt layered on top of the global defaults for this specific target."
|
|
524
|
-
rows={6}
|
|
525
|
-
value={state.prompt}
|
|
526
|
-
/>
|
|
527
|
-
</Box>
|
|
528
|
-
</Box>
|
|
529
|
-
|
|
530
|
-
<SettingsReferenceImagesEditor
|
|
531
|
-
images={state.referenceImages}
|
|
532
|
-
label="Target reference images"
|
|
533
|
-
onAdd={(files) =>
|
|
534
|
-
setTargetState((currentState) => ({
|
|
535
|
-
...currentState,
|
|
536
|
-
[target.id]: {
|
|
537
|
-
...state,
|
|
538
|
-
referenceImages: appendReferenceImages(
|
|
539
|
-
state.referenceImages,
|
|
540
|
-
files
|
|
541
|
-
),
|
|
542
|
-
},
|
|
543
|
-
}))
|
|
544
|
-
}
|
|
545
|
-
onRemove={(imageId) =>
|
|
546
|
-
setTargetState((currentState) => ({
|
|
547
|
-
...currentState,
|
|
548
|
-
[target.id]: {
|
|
549
|
-
...state,
|
|
550
|
-
referenceImages: state.referenceImages.filter(
|
|
551
|
-
(image) => image.id !== imageId
|
|
552
|
-
),
|
|
553
|
-
},
|
|
554
|
-
}))
|
|
555
|
-
}
|
|
556
|
-
/>
|
|
557
|
-
</Stack>
|
|
558
|
-
</Card>
|
|
559
|
-
)
|
|
560
|
-
})}
|
|
561
|
-
</Grid>
|
|
562
|
-
|
|
563
|
-
{error ? (
|
|
564
|
-
<Card padding={3} radius={2} tone="critical">
|
|
565
|
-
<Text size={1}>{error}</Text>
|
|
566
|
-
</Card>
|
|
567
|
-
) : null}
|
|
568
|
-
|
|
569
|
-
{saveMessage ? (
|
|
570
|
-
<Card padding={3} radius={2} tone="positive">
|
|
571
|
-
<Text size={1}>{saveMessage}</Text>
|
|
572
|
-
</Card>
|
|
573
|
-
) : null}
|
|
574
|
-
|
|
575
|
-
<Flex justify="flex-end">
|
|
576
|
-
<Button
|
|
577
|
-
disabled={isSaving}
|
|
578
|
-
onClick={() => void handleSave()}
|
|
579
|
-
text={isSaving ? "Saving..." : "Save settings"}
|
|
580
|
-
tone="primary"
|
|
581
|
-
/>
|
|
582
|
-
</Flex>
|
|
583
|
-
</Stack>
|
|
584
|
-
</Box>
|
|
585
|
-
)
|
|
586
|
-
}
|
|
587
|
-
}
|