@basementstudio/sanity-ai-image-plugin 0.0.1 → 0.1.1

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 (118) hide show
  1. package/README.md +300 -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 +169 -0
  33. package/dist/studio/components/asset-source.js.map +1 -0
  34. package/dist/studio/components/frontend-rate-limit.d.ts +29 -0
  35. package/dist/studio/components/frontend-rate-limit.d.ts.map +1 -0
  36. package/dist/studio/components/frontend-rate-limit.js +197 -0
  37. package/dist/studio/components/frontend-rate-limit.js.map +1 -0
  38. package/dist/studio/components/generate-button-input.d.ts +8 -0
  39. package/dist/studio/components/generate-button-input.d.ts.map +1 -0
  40. package/dist/studio/components/generate-button-input.js +194 -0
  41. package/dist/studio/components/generate-button-input.js.map +1 -0
  42. package/dist/studio/components/input-router.d.ts +7 -0
  43. package/dist/studio/components/input-router.d.ts.map +1 -0
  44. package/dist/studio/components/input-router.js +21 -0
  45. package/dist/studio/components/input-router.js.map +1 -0
  46. package/dist/studio/files.d.ts +9 -0
  47. package/dist/studio/files.d.ts.map +1 -0
  48. package/dist/studio/files.js +86 -0
  49. package/dist/studio/files.js.map +1 -0
  50. package/dist/studio/frontend-rate-limit.d.ts +29 -0
  51. package/dist/studio/frontend-rate-limit.d.ts.map +1 -0
  52. package/dist/studio/frontend-rate-limit.js +197 -0
  53. package/dist/studio/frontend-rate-limit.js.map +1 -0
  54. package/dist/studio/plugin.d.ts +3 -0
  55. package/dist/studio/plugin.d.ts.map +1 -0
  56. package/dist/studio/plugin.js +47 -0
  57. package/dist/studio/plugin.js.map +1 -0
  58. package/dist/studio/settings/schema.d.ts +8 -0
  59. package/dist/studio/settings/schema.d.ts.map +1 -0
  60. package/dist/studio/settings/schema.js +110 -0
  61. package/dist/studio/settings/schema.js.map +1 -0
  62. package/dist/studio/settings/tool.d.ts +3 -0
  63. package/dist/studio/settings/tool.d.ts.map +1 -0
  64. package/dist/studio/settings/tool.js +292 -0
  65. package/dist/studio/settings/tool.js.map +1 -0
  66. package/dist/studio/settings-data.d.ts +24 -0
  67. package/dist/studio/settings-data.d.ts.map +1 -0
  68. package/dist/studio/settings-data.js +99 -0
  69. package/dist/studio/settings-data.js.map +1 -0
  70. package/dist/studio/shared-secret.d.ts +9 -0
  71. package/dist/studio/shared-secret.d.ts.map +1 -0
  72. package/dist/studio/shared-secret.js +37 -0
  73. package/dist/studio/shared-secret.js.map +1 -0
  74. package/dist/utils/config.d.ts +3 -0
  75. package/dist/utils/config.d.ts.map +1 -0
  76. package/dist/utils/config.js +59 -0
  77. package/dist/utils/config.js.map +1 -0
  78. package/dist/utils/context-fields.d.ts +16 -0
  79. package/dist/utils/context-fields.d.ts.map +1 -0
  80. package/dist/utils/context-fields.js +104 -0
  81. package/dist/utils/context-fields.js.map +1 -0
  82. package/dist/utils/document-paths.d.ts +10 -0
  83. package/dist/utils/document-paths.d.ts.map +1 -0
  84. package/dist/utils/document-paths.js +33 -0
  85. package/dist/utils/document-paths.js.map +1 -0
  86. package/dist/utils/models.d.ts +22 -0
  87. package/dist/utils/models.d.ts.map +1 -0
  88. package/dist/utils/models.js +76 -0
  89. package/dist/utils/models.js.map +1 -0
  90. package/dist/utils/prompts.d.ts +2 -0
  91. package/dist/utils/prompts.d.ts.map +1 -0
  92. package/dist/utils/prompts.js +7 -0
  93. package/dist/utils/prompts.js.map +1 -0
  94. package/dist/utils/shared.d.ts +96 -0
  95. package/dist/utils/shared.d.ts.map +1 -0
  96. package/dist/utils/shared.js +17 -0
  97. package/dist/utils/shared.js.map +1 -0
  98. package/package.json +73 -67
  99. package/src/index.ts +0 -23
  100. package/src/presets/article-featured-image.ts +0 -23
  101. package/src/server/handle-request.ts +0 -207
  102. package/src/server/types.ts +0 -30
  103. package/src/server/utils.ts +0 -395
  104. package/src/server.ts +0 -14
  105. package/src/studio/components/asset-source.tsx +0 -297
  106. package/src/studio/components/generate-button-input.tsx +0 -380
  107. package/src/studio/components/input-router.tsx +0 -41
  108. package/src/studio/files.ts +0 -114
  109. package/src/studio/plugin.tsx +0 -54
  110. package/src/studio/settings/schema.ts +0 -122
  111. package/src/studio/settings/tool.tsx +0 -587
  112. package/src/studio/settings-data.ts +0 -172
  113. package/src/utils/config.ts +0 -55
  114. package/src/utils/context-fields.ts +0 -172
  115. package/src/utils/document-paths.ts +0 -51
  116. package/src/utils/models.ts +0 -126
  117. package/src/utils/prompts.ts +0 -6
  118. package/src/utils/shared.ts +0 -88
@@ -0,0 +1,169 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useSecrets } from "@sanity/studio-secrets";
4
+ import { Box, Button, Card, Flex, Select, Stack, Text, TextArea, } from "@sanity/ui";
5
+ import { useEffect, useMemo, useRef, useState } from "react";
6
+ import { useClient, } from "sanity";
7
+ import { getDefaultAllowedAiImageModel, getSupportedAiImageModelOptions, } from "../../utils/models";
8
+ import { MAX_REFERENCE_IMAGES, SETTINGS_DOCUMENT_ID, SETTINGS_SCHEMA_TYPE, SOURCE_NAME, SOURCE_TITLE, } from "../../utils/shared";
9
+ import { getMissingSharedSecretMessage, getSharedSecretValue, SHARED_SECRET_HEADER_NAME, SHARED_SECRET_NAMESPACE, } from "../shared-secret";
10
+ import { composePrompt } from "../../utils/prompts";
11
+ import { buildSettingsReferenceImages, fetchSettingsDocument, findTargetSettings, getErrorMessage, resolveSettingsModel, } from "../settings-data";
12
+ import { formatRateLimitRemainingTime, useFrontendRateLimit, } from "./frontend-rate-limit";
13
+ import { convertImageFileToPng, createGeneratedFile, formatFileSize, } from "../files";
14
+ function getGenerationPhaseLabel(phase) {
15
+ switch (phase) {
16
+ case "preparing":
17
+ return "Preparing...";
18
+ case "generating":
19
+ return "Generating...";
20
+ case "uploading":
21
+ return "Uploading to Sanity...";
22
+ case "updating":
23
+ return "Updating field...";
24
+ default:
25
+ return "Generate with AI Image Plugin";
26
+ }
27
+ }
28
+ export function createAssetSource(options) {
29
+ const assetSourceTarget = options.assetSourceTarget;
30
+ function AssetSourceComponent({ onClose, onSelect, }) {
31
+ const client = useClient({ apiVersion: options.apiVersion });
32
+ const { loading: isLoadingSharedSecret, secrets } = useSecrets(SHARED_SECRET_NAMESPACE);
33
+ const [prompt, setPrompt] = useState("");
34
+ const [selectedModel, setSelectedModel] = useState(getDefaultAllowedAiImageModel(options.allowedModels));
35
+ const [referenceImages, setReferenceImages] = useState([]);
36
+ const [error, setError] = useState(null);
37
+ const [generationPhase, setGenerationPhase] = useState("idle");
38
+ const isGenerating = generationPhase !== "idle";
39
+ const sharedSecret = getSharedSecretValue(secrets);
40
+ const hasCustomSelectedModelRef = useRef(false);
41
+ const frontendRateLimit = useFrontendRateLimit(options.frontendRateLimit);
42
+ const allowedModelOptions = useMemo(() => getSupportedAiImageModelOptions(options.allowedModels), [options.allowedModels]);
43
+ useEffect(() => {
44
+ let isMounted = true;
45
+ setSelectedModel(getDefaultAllowedAiImageModel(options.allowedModels));
46
+ hasCustomSelectedModelRef.current = false;
47
+ async function loadDefaultModel() {
48
+ try {
49
+ const settings = await fetchSettingsDocument(client, SETTINGS_DOCUMENT_ID, SETTINGS_SCHEMA_TYPE);
50
+ if (!isMounted || hasCustomSelectedModelRef.current) {
51
+ return;
52
+ }
53
+ setSelectedModel(resolveSettingsModel({
54
+ allowedModels: options.allowedModels,
55
+ settings,
56
+ }));
57
+ }
58
+ catch {
59
+ // Keep the first allowed model selected if settings loading fails.
60
+ }
61
+ }
62
+ void loadDefaultModel();
63
+ return () => {
64
+ isMounted = false;
65
+ };
66
+ }, [client, options.allowedModels]);
67
+ function handleReferenceImageChange(event) {
68
+ const nextFiles = Array.from(event.currentTarget.files ?? []);
69
+ if (nextFiles.length === 0) {
70
+ return;
71
+ }
72
+ setReferenceImages((currentFiles) => [...currentFiles, ...nextFiles].slice(0, MAX_REFERENCE_IMAGES));
73
+ event.currentTarget.value = "";
74
+ }
75
+ function handleRemoveReferenceImage(indexToRemove) {
76
+ setReferenceImages((currentFiles) => currentFiles.filter((_, index) => index !== indexToRemove));
77
+ }
78
+ async function handleGenerate() {
79
+ setError(null);
80
+ setGenerationPhase("preparing");
81
+ try {
82
+ if (isLoadingSharedSecret) {
83
+ throw new Error("Loading AI Image Plugin shared secret...");
84
+ }
85
+ if (!sharedSecret) {
86
+ throw new Error(getMissingSharedSecretMessage());
87
+ }
88
+ const settings = await fetchSettingsDocument(client, SETTINGS_DOCUMENT_ID, SETTINGS_SCHEMA_TYPE);
89
+ const targetSettings = assetSourceTarget
90
+ ? findTargetSettings(settings, assetSourceTarget.id)
91
+ : null;
92
+ const composedPrompt = composePrompt([
93
+ settings?.globalPrompt
94
+ ? `Global direction:\n${settings.globalPrompt}`
95
+ : null,
96
+ targetSettings?.prompt
97
+ ? `Asset source direction:\n${targetSettings.prompt}`
98
+ : null,
99
+ prompt.trim() ? `Editor prompt:\n${prompt.trim()}` : null,
100
+ ]);
101
+ if (!composedPrompt) {
102
+ throw new Error("Add a prompt or configure a default prompt first.");
103
+ }
104
+ const formData = new FormData();
105
+ const normalizedReferenceImages = await Promise.all(referenceImages.map(convertImageFileToPng));
106
+ const settingsReferenceImages = await buildSettingsReferenceImages(settings, assetSourceTarget?.id);
107
+ const allReferenceImages = [
108
+ ...settingsReferenceImages,
109
+ ...normalizedReferenceImages,
110
+ ].slice(0, MAX_REFERENCE_IMAGES);
111
+ formData.set("prompt", composedPrompt);
112
+ formData.set("model", selectedModel);
113
+ for (const referenceImage of allReferenceImages) {
114
+ formData.append("references", referenceImage);
115
+ }
116
+ const rateLimitSnapshot = frontendRateLimit.consumeAttempt();
117
+ if (!rateLimitSnapshot.allowed) {
118
+ return;
119
+ }
120
+ setGenerationPhase("generating");
121
+ const response = await fetch(options.apiEndpoint, {
122
+ method: "POST",
123
+ body: formData,
124
+ headers: {
125
+ [SHARED_SECRET_HEADER_NAME]: sharedSecret,
126
+ },
127
+ });
128
+ const payload = (await response.json());
129
+ if (!response.ok) {
130
+ throw new Error("error" in payload && payload.error
131
+ ? payload.error
132
+ : "AI image generation failed.");
133
+ }
134
+ if (!("data" in payload) ||
135
+ typeof payload.data !== "string" ||
136
+ typeof payload.mimeType !== "string") {
137
+ throw new Error("AI Image Plugin returned an invalid image payload.");
138
+ }
139
+ const generatedFile = createGeneratedFile(payload, prompt, "ai-image-plugin");
140
+ setGenerationPhase("uploading");
141
+ const uploadedAsset = await client.assets.upload("image", generatedFile, {
142
+ filename: generatedFile.name,
143
+ });
144
+ setGenerationPhase("updating");
145
+ onSelect([{ kind: "assetDocumentId", value: uploadedAsset._id }]);
146
+ onClose();
147
+ }
148
+ catch (nextError) {
149
+ setError(getErrorMessage(nextError));
150
+ }
151
+ finally {
152
+ setGenerationPhase("idle");
153
+ }
154
+ }
155
+ return (_jsx(Card, { padding: 4, children: _jsxs(Stack, { space: 4, children: [_jsxs(Stack, { space: 2, children: [_jsx(Text, { size: 2, weight: "semibold", children: "Generate an image with AI Image Plugin and drop it straight into this field." }), _jsx(Text, { muted: true, size: 1, children: "Project defaults from the AI Image Plugin settings tool are included automatically when configured." })] }), _jsxs(Box, { children: [_jsx(Text, { size: 1, weight: "medium", children: "Model" }), _jsx(Box, { marginTop: 2, children: _jsx(Select, { onChange: (event) => {
156
+ hasCustomSelectedModelRef.current = true;
157
+ setSelectedModel(event.currentTarget.value);
158
+ }, value: selectedModel, children: allowedModelOptions.map((modelOption) => (_jsx("option", { value: modelOption.value, children: modelOption.title }, modelOption.value))) }) })] }), _jsxs(Box, { children: [_jsx(Text, { size: 1, weight: "medium", children: assetSourceTarget?.promptLabel || "Prompt" }), _jsx(Box, { marginTop: 2, children: _jsx(TextArea, { onChange: (event) => setPrompt(event.currentTarget.value), placeholder: assetSourceTarget?.promptPlaceholder ||
159
+ "Turn these references into a clean homepage hero image with warm daylight and subtle depth.", rows: 6, value: prompt }) })] }), _jsxs(Box, { children: [_jsx(Text, { size: 1, weight: "medium", children: "Reference images" }), _jsx(Box, { marginTop: 2, children: _jsx("input", { accept: "image/*", multiple: true, onChange: handleReferenceImageChange, type: "file" }) }), referenceImages.length > 0 ? (_jsx(Box, { marginTop: 3, children: _jsx(Stack, { space: 2, children: referenceImages.map((referenceImage, index) => (_jsx(Card, { padding: 3, radius: 2, tone: "transparent", children: _jsxs(Flex, { align: "center", gap: 3, justify: "space-between", children: [_jsxs(Box, { flex: 1, children: [_jsx(Text, { size: 1, weight: "medium", children: referenceImage.name }), _jsx(Text, { muted: true, size: 1, children: formatFileSize(referenceImage.size) })] }), _jsx(Button, { mode: "bleed", onClick: () => handleRemoveReferenceImage(index), text: "Remove" })] }) }, `${referenceImage.name}-${index}`))) }) })) : null] }), error ? (_jsx(Card, { padding: 3, radius: 2, tone: "critical", children: _jsx(Text, { size: 1, children: error }) })) : null, frontendRateLimit.blocked && !isGenerating ? (_jsx(Card, { padding: 3, radius: 2, tone: "caution", children: _jsxs(Text, { size: 1, children: ["Please wait", " ", formatRateLimitRemainingTime(frontendRateLimit.remainingMs), " ", "before generating again."] }) })) : null, isGenerating ? (_jsx(Card, { padding: 3, radius: 2, tone: "transparent", children: _jsxs(Text, { size: 1, children: [getGenerationPhaseLabel(generationPhase), " This can take a minute or two for slower models."] }) })) : null, _jsxs(Flex, { gap: 3, justify: "flex-end", children: [_jsx(Button, { mode: "ghost", onClick: onClose, text: "Cancel" }), _jsx(Button, { disabled: isGenerating ||
160
+ isLoadingSharedSecret ||
161
+ frontendRateLimit.blocked, onClick: () => void handleGenerate(), text: getGenerationPhaseLabel(generationPhase), tone: "primary" })] })] }) }));
162
+ }
163
+ return {
164
+ name: SOURCE_NAME,
165
+ title: SOURCE_TITLE,
166
+ component: AssetSourceComponent,
167
+ };
168
+ }
169
+ //# sourceMappingURL=asset-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-source.js","sourceRoot":"","sources":["../../../src/studio/components/asset-source.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { ResolvedFrontendRateLimitOptions } from "../../utils/shared";
2
+ export declare const FRONTEND_RATE_LIMIT_STORAGE_KEY = "ai-image-plugin.frontend-rate-limit.v1";
3
+ type FrontendRateLimitState = {
4
+ attempts: number[];
5
+ };
6
+ type FrontendRateLimitEnvironment = {
7
+ fallbackState?: FrontendRateLimitState;
8
+ storage?: Pick<Storage, "getItem" | "setItem"> | null;
9
+ };
10
+ export type FrontendRateLimitSnapshot = {
11
+ attempts: number[];
12
+ blocked: boolean;
13
+ remainingMs: number;
14
+ };
15
+ export type FrontendRateLimitAttemptResult = FrontendRateLimitSnapshot & {
16
+ allowed: boolean;
17
+ };
18
+ export declare function createFrontendRateLimiter(options: ResolvedFrontendRateLimitOptions | false, environment?: FrontendRateLimitEnvironment): {
19
+ consumeAttempt(now?: number): FrontendRateLimitAttemptResult;
20
+ getSnapshot(now?: number): FrontendRateLimitSnapshot;
21
+ };
22
+ export declare function formatRateLimitRemainingTime(remainingMs: number): string;
23
+ export declare function useFrontendRateLimit(options: ResolvedFrontendRateLimitOptions | false): {
24
+ blocked: boolean;
25
+ consumeAttempt: () => FrontendRateLimitAttemptResult;
26
+ remainingMs: number;
27
+ };
28
+ export {};
29
+ //# sourceMappingURL=frontend-rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontend-rate-limit.d.ts","sourceRoot":"","sources":["../../../src/studio/components/frontend-rate-limit.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,oBAAoB,CAAC;AAE3E,eAAO,MAAM,+BAA+B,2CACF,CAAC;AAI3C,KAAK,sBAAsB,GAAG;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF,KAAK,4BAA4B,GAAG;IAClC,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG,yBAAyB,GAAG;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAiKF,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GAAG,KAAK,EACjD,WAAW,CAAC,EAAE,4BAA4B;kCAGN,8BAA8B;+BAsBjC,yBAAyB;EAI3D;AAED,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAMxE;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,gCAAgC,GAAG,KAAK;;0BAuDtB,8BAA8B;;EAa1D"}
@@ -0,0 +1,197 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ export const FRONTEND_RATE_LIMIT_STORAGE_KEY = "ai-image-plugin.frontend-rate-limit.v1";
4
+ const FRONTEND_RATE_LIMIT_SYNC_EVENT = "ai-image-plugin:frontend-rate-limit";
5
+ const memoryFallbackState = {
6
+ attempts: [],
7
+ };
8
+ function createEmptySnapshot() {
9
+ return {
10
+ attempts: [],
11
+ blocked: false,
12
+ remainingMs: 0,
13
+ };
14
+ }
15
+ function getWindowMs(options) {
16
+ return options.windowSecs * 1000;
17
+ }
18
+ function hasSameAttempts(left, right) {
19
+ if (left.length !== right.length) {
20
+ return false;
21
+ }
22
+ return left.every((value, index) => value === right[index]);
23
+ }
24
+ function pruneAttempts(attempts, windowMs, now) {
25
+ return attempts.filter((attempt) => now - attempt < windowMs);
26
+ }
27
+ function parseAttempts(value) {
28
+ if (!value) {
29
+ return [];
30
+ }
31
+ try {
32
+ const parsedValue = JSON.parse(value);
33
+ if (!Array.isArray(parsedValue.attempts)) {
34
+ return [];
35
+ }
36
+ return parsedValue.attempts
37
+ .filter((attempt) => typeof attempt === "number" && Number.isFinite(attempt))
38
+ .sort((left, right) => left - right);
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ }
44
+ function resolveStorage(storage) {
45
+ if (storage !== undefined) {
46
+ return storage;
47
+ }
48
+ if (typeof window === "undefined") {
49
+ return null;
50
+ }
51
+ try {
52
+ return window.localStorage;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ function readAttempts(storage, fallbackState) {
59
+ if (!storage) {
60
+ return [...fallbackState.attempts];
61
+ }
62
+ try {
63
+ return parseAttempts(storage.getItem(FRONTEND_RATE_LIMIT_STORAGE_KEY));
64
+ }
65
+ catch {
66
+ return [...fallbackState.attempts];
67
+ }
68
+ }
69
+ function persistAttempts(attempts, storage, fallbackState) {
70
+ fallbackState.attempts = [...attempts];
71
+ if (!storage) {
72
+ return;
73
+ }
74
+ try {
75
+ storage.setItem(FRONTEND_RATE_LIMIT_STORAGE_KEY, JSON.stringify({ attempts }));
76
+ }
77
+ catch {
78
+ // Keep the in-memory fallback current when storage writes fail.
79
+ }
80
+ }
81
+ function notifyAttemptListeners() {
82
+ if (typeof window === "undefined") {
83
+ return;
84
+ }
85
+ window.dispatchEvent(new Event(FRONTEND_RATE_LIMIT_SYNC_EVENT));
86
+ }
87
+ function buildSnapshot(attempts, options, now) {
88
+ const windowMs = getWindowMs(options);
89
+ const blocked = attempts.length >= options.maxRequests;
90
+ const relevantAttempt = blocked
91
+ ? attempts[attempts.length - options.maxRequests]
92
+ : null;
93
+ return {
94
+ attempts,
95
+ blocked,
96
+ remainingMs: relevantAttempt === null
97
+ ? 0
98
+ : Math.max(0, relevantAttempt + windowMs - now),
99
+ };
100
+ }
101
+ function readSnapshot(options, environment, now = Date.now()) {
102
+ if (!options) {
103
+ return createEmptySnapshot();
104
+ }
105
+ const fallbackState = environment?.fallbackState || memoryFallbackState;
106
+ const storage = resolveStorage(environment?.storage);
107
+ const storedAttempts = readAttempts(storage, fallbackState);
108
+ const attempts = pruneAttempts(storedAttempts, getWindowMs(options), now);
109
+ if (!hasSameAttempts(storedAttempts, attempts)) {
110
+ persistAttempts(attempts, storage, fallbackState);
111
+ }
112
+ return buildSnapshot(attempts, options, now);
113
+ }
114
+ export function createFrontendRateLimiter(options, environment) {
115
+ return {
116
+ consumeAttempt(now = Date.now()) {
117
+ const currentSnapshot = readSnapshot(options, environment, now);
118
+ if (!options || currentSnapshot.blocked) {
119
+ return {
120
+ ...currentSnapshot,
121
+ allowed: !currentSnapshot.blocked,
122
+ };
123
+ }
124
+ const fallbackState = environment?.fallbackState || memoryFallbackState;
125
+ const storage = resolveStorage(environment?.storage);
126
+ const attempts = [...currentSnapshot.attempts, now];
127
+ persistAttempts(attempts, storage, fallbackState);
128
+ notifyAttemptListeners();
129
+ return {
130
+ ...buildSnapshot(attempts, options, now),
131
+ allowed: true,
132
+ };
133
+ },
134
+ getSnapshot(now = Date.now()) {
135
+ return readSnapshot(options, environment, now);
136
+ },
137
+ };
138
+ }
139
+ export function formatRateLimitRemainingTime(remainingMs) {
140
+ const totalSeconds = Math.max(0, Math.ceil(remainingMs / 1000));
141
+ const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, "0");
142
+ const seconds = String(totalSeconds % 60).padStart(2, "0");
143
+ return `${minutes}:${seconds}`;
144
+ }
145
+ export function useFrontendRateLimit(options) {
146
+ const maxRequests = options ? options.maxRequests : 0;
147
+ const windowSecs = options ? options.windowSecs : 0;
148
+ const [snapshot, setSnapshot] = useState(() => createFrontendRateLimiter(options).getSnapshot());
149
+ useEffect(() => {
150
+ setSnapshot(createFrontendRateLimiter(options).getSnapshot());
151
+ }, [maxRequests, windowSecs]);
152
+ useEffect(() => {
153
+ if (typeof window === "undefined") {
154
+ return;
155
+ }
156
+ function syncSnapshot() {
157
+ setSnapshot(createFrontendRateLimiter(options).getSnapshot());
158
+ }
159
+ function handleStorage(event) {
160
+ if (!event.key || event.key === FRONTEND_RATE_LIMIT_STORAGE_KEY) {
161
+ syncSnapshot();
162
+ }
163
+ }
164
+ function handleSync() {
165
+ syncSnapshot();
166
+ }
167
+ syncSnapshot();
168
+ window.addEventListener("storage", handleStorage);
169
+ window.addEventListener(FRONTEND_RATE_LIMIT_SYNC_EVENT, handleSync);
170
+ return () => {
171
+ window.removeEventListener("storage", handleStorage);
172
+ window.removeEventListener(FRONTEND_RATE_LIMIT_SYNC_EVENT, handleSync);
173
+ };
174
+ }, [maxRequests, windowSecs]);
175
+ useEffect(() => {
176
+ if (!snapshot.blocked || typeof window === "undefined") {
177
+ return;
178
+ }
179
+ const intervalId = window.setInterval(() => {
180
+ setSnapshot(createFrontendRateLimiter(options).getSnapshot());
181
+ }, 1000);
182
+ return () => {
183
+ window.clearInterval(intervalId);
184
+ };
185
+ }, [snapshot.blocked, maxRequests, windowSecs]);
186
+ function consumeAttempt() {
187
+ const nextSnapshot = createFrontendRateLimiter(options).consumeAttempt();
188
+ setSnapshot(nextSnapshot);
189
+ return nextSnapshot;
190
+ }
191
+ return {
192
+ blocked: snapshot.blocked,
193
+ consumeAttempt,
194
+ remainingMs: snapshot.remainingMs,
195
+ };
196
+ }
197
+ //# sourceMappingURL=frontend-rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontend-rate-limit.js","sourceRoot":"","sources":["../../../src/studio/components/frontend-rate-limit.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG5C,MAAM,CAAC,MAAM,+BAA+B,GAC1C,wCAAwC,CAAC;AAE3C,MAAM,8BAA8B,GAAG,qCAAqC,CAAC;AAqB7E,MAAM,mBAAmB,GAA2B;IAClD,QAAQ,EAAE,EAAE;CACb,CAAC;AAEF,SAAS,mBAAmB;IAC1B,OAAO;QACL,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,KAAK;QACd,WAAW,EAAE,CAAC;KACf,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,OAAyC;IAC5D,OAAO,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;AACnC,CAAC;AAED,SAAS,eAAe,CAAC,IAAc,EAAE,KAAe;IACtD,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,aAAa,CACpB,QAAkB,EAClB,QAAgB,EAChB,GAAW;IAEX,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,GAAG,OAAO,GAAG,QAAQ,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,aAAa,CAAC,KAAoB;IACzC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAA2B,CAAC;QAEhE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,WAAW,CAAC,QAAQ;aACxB,MAAM,CACL,CAAC,OAAO,EAAqB,EAAE,CAC7B,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAC1D;aACA,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CACrB,OAAgD;IAEhD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,YAAY,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CACnB,OAAoD,EACpD,aAAqC;IAErC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,CAAC;QACH,OAAO,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CACtB,QAAkB,EAClB,OAAoD,EACpD,aAAqC;IAErC,aAAa,CAAC,QAAQ,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;IAEvC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,OAAO,CAAC,OAAO,CACb,+BAA+B,EAC/B,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC,CAC7B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,gEAAgE;IAClE,CAAC;AACH,CAAC;AAED,SAAS,sBAAsB;IAC7B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO;IACT,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,aAAa,CACpB,QAAkB,EAClB,OAAyC,EACzC,GAAW;IAEX,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC;IACvD,MAAM,eAAe,GAAG,OAAO;QAC7B,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;QACjD,CAAC,CAAC,IAAI,CAAC;IAET,OAAO;QACL,QAAQ;QACR,OAAO;QACP,WAAW,EACT,eAAe,KAAK,IAAI;YACtB,CAAC,CAAC,CAAC;YACH,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,GAAG,QAAQ,GAAG,GAAG,CAAC;KACpD,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CACnB,OAAiD,EACjD,WAA0C,EAC1C,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAEhB,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,mBAAmB,EAAE,CAAC;IAC/B,CAAC;IAED,MAAM,aAAa,GAAG,WAAW,EAAE,aAAa,IAAI,mBAAmB,CAAC;IACxE,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,cAAc,GAAG,YAAY,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,aAAa,CAAC,cAAc,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC;IAE1E,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC/C,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAiD,EACjD,WAA0C;IAE1C,OAAO;QACL,cAAc,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;YAC7B,MAAM,eAAe,GAAG,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;YAEhE,IAAI,CAAC,OAAO,IAAI,eAAe,CAAC,OAAO,EAAE,CAAC;gBACxC,OAAO;oBACL,GAAG,eAAe;oBAClB,OAAO,EAAE,CAAC,eAAe,CAAC,OAAO;iBAClC,CAAC;YACJ,CAAC;YAED,MAAM,aAAa,GAAG,WAAW,EAAE,aAAa,IAAI,mBAAmB,CAAC;YACxE,MAAM,OAAO,GAAG,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;YACrD,MAAM,QAAQ,GAAG,CAAC,GAAG,eAAe,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAEpD,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;YAClD,sBAAsB,EAAE,CAAC;YAEzB,OAAO;gBACL,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC;gBACxC,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QACD,WAAW,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;YAC1B,OAAO,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,WAAmB;IAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAE3D,OAAO,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,OAAiD;IAEjD,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAA4B,GAAG,EAAE,CACvE,yBAAyB,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CACjD,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,WAAW,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,CAAC,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IAE9B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,SAAS,YAAY;YACnB,WAAW,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,SAAS,aAAa,CAAC,KAAmB;YACxC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,+BAA+B,EAAE,CAAC;gBAChE,YAAY,EAAE,CAAC;YACjB,CAAC;QACH,CAAC;QAED,SAAS,UAAU;YACjB,YAAY,EAAE,CAAC;QACjB,CAAC;QAED,YAAY,EAAE,CAAC;QACf,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAClD,MAAM,CAAC,gBAAgB,CAAC,8BAA8B,EAAE,UAAU,CAAC,CAAC;QAEpE,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;YACrD,MAAM,CAAC,mBAAmB,CAAC,8BAA8B,EAAE,UAAU,CAAC,CAAC;QACzE,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IAE9B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;YACvD,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;YACzC,WAAW,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAChE,CAAC,EAAE,IAAI,CAAC,CAAC;QAET,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACnC,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IAEhD,SAAS,cAAc;QACrB,MAAM,YAAY,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC;QAEzE,WAAW,CAAC,YAAY,CAAC,CAAC;QAE1B,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,OAAO;QACL,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,cAAc;QACd,WAAW,EAAE,QAAQ,CAAC,WAAW;KAClC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type ImageValue, type ObjectInputProps } from "sanity";
2
+ import { type GenerateButtonTarget, type ResolvedOptions } from "../../utils/shared";
3
+ export declare function GenerateButtonInput({ options, props, target, }: {
4
+ options: ResolvedOptions;
5
+ props: ObjectInputProps<ImageValue>;
6
+ target: GenerateButtonTarget;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=generate-button-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-button-input.d.ts","sourceRoot":"","sources":["../../../src/studio/components/generate-button-input.tsx"],"names":[],"mappings":"AAeA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,gBAAgB,EAMtB,MAAM,QAAQ,CAAC;AAkBhB,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AA0C5B,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,KAAK,EACL,MAAM,GACP,EAAE;IACD,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,EAAE,oBAAoB,CAAC;CAC9B,2CA6ZA"}
@@ -0,0 +1,194 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Box, Button, Card, Dialog, Flex, Select, Stack, Text, TextArea, } from "@sanity/ui";
4
+ import { useSecrets } from "@sanity/studio-secrets";
5
+ import { useEffect, useMemo, useRef, useState } from "react";
6
+ import { PatchEvent, set, setIfMissing, useClient, useFormValue, useSchema, } from "sanity";
7
+ import { getMissingSharedSecretMessage, getSharedSecretValue, SHARED_SECRET_HEADER_NAME, SHARED_SECRET_NAMESPACE, } from "../shared-secret";
8
+ import { buildContextFieldPrompt, getDefaultSelectedContextFieldPaths, getSelectableContextFields, } from "../../utils/context-fields";
9
+ import { getDefaultAllowedAiImageModel, getSupportedAiImageModelOptions, } from "../../utils/models";
10
+ import { SETTINGS_DOCUMENT_ID, SETTINGS_SCHEMA_TYPE, } from "../../utils/shared";
11
+ import { composePrompt } from "../../utils/prompts";
12
+ import { buildSettingsReferenceImages, fetchSettingsDocument, findTargetSettings, getErrorMessage, resolveSettingsModel, } from "../settings-data";
13
+ import { formatRateLimitRemainingTime, useFrontendRateLimit, } from "./frontend-rate-limit";
14
+ import { createGeneratedFile } from "../files";
15
+ function getGenerationPhaseLabel(phase) {
16
+ switch (phase) {
17
+ case "preparing":
18
+ return "Preparing...";
19
+ case "generating":
20
+ return "Generating...";
21
+ case "uploading":
22
+ return "Uploading to Sanity...";
23
+ case "updating":
24
+ return "Updating field...";
25
+ default:
26
+ return "Generate image";
27
+ }
28
+ }
29
+ export function GenerateButtonInput({ options, props, target, }) {
30
+ const client = useClient({ apiVersion: options.apiVersion });
31
+ const { loading: isLoadingSharedSecret, secrets } = useSecrets(SHARED_SECRET_NAMESPACE);
32
+ const schema = useSchema();
33
+ const documentValue = useFormValue([]);
34
+ const documentType = useFormValue(["_type"]);
35
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
36
+ const [userPrompt, setUserPrompt] = useState("");
37
+ const [selectedModel, setSelectedModel] = useState(getDefaultAllowedAiImageModel(options.allowedModels));
38
+ const [selectedContextFieldPaths, setSelectedContextFieldPaths] = useState([]);
39
+ const [error, setError] = useState(null);
40
+ const [generationPhase, setGenerationPhase] = useState("idle");
41
+ const isGenerating = generationPhase !== "idle";
42
+ const currentAssetRef = props.value?.asset?._ref || "empty";
43
+ const hasCustomSelectedModelRef = useRef(false);
44
+ const frontendRateLimit = useFrontendRateLimit(options.frontendRateLimit);
45
+ const selectableContextFields = useMemo(() => getSelectableContextFields(documentType ? schema.get(documentType) : undefined), [documentType, schema]);
46
+ const defaultSelectedContextFieldPaths = useMemo(() => getDefaultSelectedContextFieldPaths({
47
+ selectableContextFields,
48
+ suggestedContextFieldPaths: target.suggestedContextFieldPaths,
49
+ }), [selectableContextFields, target.suggestedContextFieldPaths]);
50
+ const runtimePrompt = buildContextFieldPrompt({
51
+ contextFieldPaths: selectedContextFieldPaths,
52
+ documentValue,
53
+ });
54
+ const sharedSecret = getSharedSecretValue(secrets);
55
+ const allowedModelOptions = useMemo(() => getSupportedAiImageModelOptions(options.allowedModels), [options.allowedModels]);
56
+ useEffect(() => {
57
+ if (!isDialogOpen) {
58
+ return;
59
+ }
60
+ let isMounted = true;
61
+ setSelectedModel(getDefaultAllowedAiImageModel(options.allowedModels));
62
+ hasCustomSelectedModelRef.current = false;
63
+ async function loadDefaultModel() {
64
+ try {
65
+ const settings = await fetchSettingsDocument(client, SETTINGS_DOCUMENT_ID, SETTINGS_SCHEMA_TYPE);
66
+ if (!isMounted || hasCustomSelectedModelRef.current) {
67
+ return;
68
+ }
69
+ setSelectedModel(resolveSettingsModel({
70
+ allowedModels: options.allowedModels,
71
+ settings,
72
+ }));
73
+ }
74
+ catch {
75
+ // Keep the first allowed model selected if settings loading fails.
76
+ }
77
+ }
78
+ void loadDefaultModel();
79
+ return () => {
80
+ isMounted = false;
81
+ };
82
+ }, [client, isDialogOpen, options.allowedModels]);
83
+ function toggleContextFieldPath(fieldPath) {
84
+ setSelectedContextFieldPaths((currentFieldPaths) => currentFieldPaths.includes(fieldPath)
85
+ ? currentFieldPaths.filter((currentFieldPath) => currentFieldPath !== fieldPath)
86
+ : [...currentFieldPaths, fieldPath]);
87
+ }
88
+ async function handleGenerate() {
89
+ setError(null);
90
+ setGenerationPhase("preparing");
91
+ try {
92
+ if (isLoadingSharedSecret) {
93
+ throw new Error("Loading AI Image Plugin shared secret...");
94
+ }
95
+ if (!sharedSecret) {
96
+ throw new Error(getMissingSharedSecretMessage());
97
+ }
98
+ const settings = await fetchSettingsDocument(client, SETTINGS_DOCUMENT_ID, SETTINGS_SCHEMA_TYPE);
99
+ const targetSettings = findTargetSettings(settings, target.id);
100
+ const prompt = composePrompt([
101
+ settings?.globalPrompt
102
+ ? `Global direction:\n${settings.globalPrompt}`
103
+ : null,
104
+ targetSettings?.prompt
105
+ ? `Target direction:\n${targetSettings.prompt}`
106
+ : null,
107
+ runtimePrompt ? `Document context:\n${runtimePrompt}` : null,
108
+ userPrompt.trim()
109
+ ? `Additional editor instructions:\n${userPrompt.trim()}`
110
+ : null,
111
+ ]);
112
+ if (!prompt) {
113
+ throw new Error("Add a prompt or configure target defaults first.");
114
+ }
115
+ const referenceImages = await buildSettingsReferenceImages(settings, target.id);
116
+ const formData = new FormData();
117
+ formData.set("prompt", prompt);
118
+ formData.set("model", selectedModel);
119
+ for (const referenceImage of referenceImages) {
120
+ formData.append("references", referenceImage);
121
+ }
122
+ const rateLimitSnapshot = frontendRateLimit.consumeAttempt();
123
+ if (!rateLimitSnapshot.allowed) {
124
+ return;
125
+ }
126
+ setGenerationPhase("generating");
127
+ const response = await fetch(options.apiEndpoint, {
128
+ method: "POST",
129
+ body: formData,
130
+ headers: {
131
+ [SHARED_SECRET_HEADER_NAME]: sharedSecret,
132
+ },
133
+ });
134
+ const payload = (await response.json());
135
+ if (!response.ok) {
136
+ throw new Error("error" in payload && payload.error
137
+ ? payload.error
138
+ : "AI image generation failed.");
139
+ }
140
+ if (!("data" in payload) ||
141
+ typeof payload.data !== "string" ||
142
+ typeof payload.mimeType !== "string") {
143
+ throw new Error("AI Image Plugin returned an invalid image payload.");
144
+ }
145
+ const basename = String(documentValue?.title ||
146
+ documentValue?._type ||
147
+ target.title ||
148
+ "generated").trim() || "generated";
149
+ const generatedFile = createGeneratedFile(payload, basename, "ai-image-plugin-field");
150
+ setGenerationPhase("uploading");
151
+ const uploadedAsset = await client.assets.upload("image", generatedFile, {
152
+ filename: generatedFile.name,
153
+ });
154
+ setGenerationPhase("updating");
155
+ props.onChange(PatchEvent.from([
156
+ setIfMissing({ _type: "image" }),
157
+ set({ _type: "reference", _ref: uploadedAsset._id }, ["asset"]),
158
+ ]));
159
+ setIsDialogOpen(false);
160
+ setUserPrompt("");
161
+ }
162
+ catch (nextError) {
163
+ setError(getErrorMessage(nextError, "Unable to generate an image for this field."));
164
+ }
165
+ finally {
166
+ setGenerationPhase("idle");
167
+ }
168
+ }
169
+ return (_jsxs(Stack, { space: 4, children: [_jsx(Card, { padding: 3, radius: 2, tone: "transparent", children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 1, children: target.description ||
170
+ "Generate an image with AI Image Plugin for this field." }), _jsx(Flex, { gap: 3, children: _jsx(Button, { onClick: () => {
171
+ setError(null);
172
+ setSelectedContextFieldPaths(defaultSelectedContextFieldPaths);
173
+ setIsDialogOpen(true);
174
+ }, text: "Generate", tone: "primary" }) })] }) }), _jsx(Box, { children: props.renderDefault(props) }, currentAssetRef), isDialogOpen ? (_jsx(Dialog, { header: target.dialogTitle || "Generate Image", id: `${target.id}-generate-dialog`, onClose: () => {
175
+ if (isGenerating) {
176
+ return;
177
+ }
178
+ setError(null);
179
+ setIsDialogOpen(false);
180
+ }, width: 1, children: _jsx(Box, { padding: 4, children: _jsxs(Stack, { space: 4, children: [_jsxs(Box, { children: [_jsx(Text, { size: 1, weight: "medium", children: "Model" }), _jsx(Box, { marginTop: 2, children: _jsx(Select, { onChange: (event) => {
181
+ hasCustomSelectedModelRef.current = true;
182
+ setSelectedModel(event.currentTarget.value);
183
+ }, value: selectedModel, children: allowedModelOptions.map((modelOption) => (_jsx("option", { value: modelOption.value, children: modelOption.title }, modelOption.value))) }) })] }), _jsxs(Box, { children: [_jsx(Text, { size: 1, weight: "medium", children: target.promptLabel || "Custom prompt" }), _jsx(Box, { marginTop: 2, children: _jsx(TextArea, { onChange: (event) => setUserPrompt(event.currentTarget.value), placeholder: target.promptPlaceholder ||
184
+ "Optional custom instructions for this image.", rows: 6, value: userPrompt }) })] }), selectableContextFields.length > 0 ? (_jsx(Box, { children: _jsx(Stack, { space: 3, children: _jsxs(Stack, { space: 2, children: [_jsx(Text, { size: 1, weight: "medium", children: "Include document fields" }), _jsx(Flex, { gap: 2, style: { flexWrap: "wrap" }, children: selectableContextFields.map((field) => {
185
+ const isSelected = selectedContextFieldPaths.includes(field.path);
186
+ return (_jsx(Button, { mode: isSelected ? "default" : "ghost", onClick: () => toggleContextFieldPath(field.path), text: field.label, tone: isSelected ? "primary" : "default" }, field.path));
187
+ }) })] }) }) })) : (_jsx(Card, { padding: 3, radius: 2, tone: "transparent", children: _jsxs(Stack, { space: 2, children: [_jsx(Text, { size: 1, weight: "medium", children: "Document context" }), _jsx(Text, { muted: true, size: 1, children: "No supported top-level document fields are available for this type yet." })] }) })), selectableContextFields.length > 0 ? (_jsx(Card, { padding: 3, radius: 2, tone: "transparent", children: _jsxs(Stack, { space: 2, children: [_jsx(Text, { size: 1, weight: "medium", children: "Document context preview" }), runtimePrompt ? (_jsx(Text, { size: 1, children: runtimePrompt })) : (_jsx(Text, { muted: true, size: 1, children: "No document fields are selected with a usable value yet." }))] }) })) : null, error ? (_jsx(Card, { padding: 3, radius: 2, tone: "critical", children: _jsx(Text, { size: 1, children: error }) })) : null, frontendRateLimit.blocked && !isGenerating ? (_jsx(Card, { padding: 3, radius: 2, tone: "caution", children: _jsxs(Text, { size: 1, children: ["Please wait", " ", formatRateLimitRemainingTime(frontendRateLimit.remainingMs), " ", "before generating again."] }) })) : null, isGenerating ? (_jsx(Card, { padding: 3, radius: 2, tone: "transparent", children: _jsxs(Text, { size: 1, children: [getGenerationPhaseLabel(generationPhase), " This can take a minute or two for slower models."] }) })) : null, _jsxs(Flex, { gap: 3, justify: "flex-end", children: [_jsx(Button, { disabled: isGenerating, mode: "ghost", onClick: () => {
188
+ setError(null);
189
+ setIsDialogOpen(false);
190
+ }, text: "Cancel" }), _jsx(Button, { disabled: isGenerating ||
191
+ isLoadingSharedSecret ||
192
+ frontendRateLimit.blocked, onClick: () => void handleGenerate(), text: getGenerationPhaseLabel(generationPhase), tone: "primary" })] })] }) }) })) : null] }));
193
+ }
194
+ //# sourceMappingURL=generate-button-input.js.map