@mvriu5/payload-ai 1.5.0 → 1.6.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.
package/README.md CHANGED
@@ -25,18 +25,18 @@ npm add @mvriu5/payload-ai @openrouter/ai-sdk-provider
25
25
  ## Usage
26
26
 
27
27
  ```ts
28
- import { buildConfig } from "payload";
29
- import { payloadAiPlugin } from "@mvriu5/payload-ai";
28
+ import { buildConfig } from "payload"
29
+ import { payloadAiPlugin } from "@mvriu5/payload-ai"
30
30
 
31
31
  export default buildConfig({
32
- plugins: [
33
- payloadAiPlugin({
34
- collections: {
35
- posts: true,
36
- },
37
- }),
38
- ],
39
- });
32
+ plugins: [
33
+ payloadAiPlugin({
34
+ collections: {
35
+ posts: true,
36
+ },
37
+ }),
38
+ ],
39
+ })
40
40
  ```
41
41
 
42
42
  Without centrally configured providers, the plugin adds two fields to the configured Payload admin user collection:
@@ -51,62 +51,72 @@ When `providers` is configured, provider selection and API keys are managed cent
51
51
  ## Options
52
52
 
53
53
  ```ts
54
- import type { PayloadAIPluginOptions } from "@mvriu5/payload-ai";
54
+ import type { PayloadAIPluginOptions } from "@mvriu5/payload-ai"
55
55
 
56
56
  const options: PayloadAIPluginOptions = {
57
- aiInput: true,
58
- allowUserApiKeys: false,
59
- collections: {
60
- media: {
61
- read: true,
62
- update: true,
57
+ aiInput: true,
58
+ allowUserApiKeys: false,
59
+ authCollections: {
60
+ aiInput: false,
61
+ generateFields: false,
63
62
  },
64
- posts: {
65
- read: true,
66
- create: true,
67
- update: true,
68
- delete: false,
63
+ collections: {
64
+ media: {
65
+ read: true,
66
+ update: true,
67
+ },
68
+ posts: {
69
+ read: true,
70
+ create: true,
71
+ update: true,
72
+ delete: false,
73
+ },
74
+ users: true,
75
+ },
76
+ generateFields: true,
77
+ media: {
78
+ enabled: true,
79
+ collectionSlug: "media",
80
+ acceptedMimeTypes: ["image/*"],
81
+ maxFileSize: 10 * 1024 * 1024,
69
82
  },
70
- users: true,
71
- },
72
- generateFields: true,
73
- media: {
74
- enabled: true,
75
- collectionSlug: "media",
76
- acceptedMimeTypes: ["image/*"],
77
- maxFileSize: 10 * 1024 * 1024,
78
- },
79
- providers: [
80
- {
81
- id: "company-openai",
82
- label: "Company OpenAI",
83
- provider: "openai",
84
- apiKey: process.env.COMPANY_OPENAI_API_KEY,
85
- models: [
86
- { label: "GPT-4.1 Mini", value: "gpt-4.1-mini" },
87
- { label: "GPT-4.1", value: "gpt-4.1" },
88
- ],
89
- defaultModel: "gpt-4.1-mini",
83
+ providers: [
84
+ {
85
+ id: "company-openai",
86
+ label: "Company OpenAI",
87
+ provider: "openai",
88
+ apiKey: process.env.COMPANY_OPENAI_API_KEY,
89
+ models: [
90
+ { label: "GPT-4.1 Mini", value: "gpt-4.1-mini" },
91
+ { label: "GPT-4.1", value: "gpt-4.1" },
92
+ ],
93
+ defaultModel: "gpt-4.1-mini",
94
+ },
95
+ {
96
+ id: "ollama",
97
+ label: "Local Ollama",
98
+ provider: "openai",
99
+ baseURL: "http://localhost:11434/v1",
100
+ apiKey: "ollama",
101
+ models: [
102
+ { label: "Llama 3.3", value: "llama3.3" },
103
+ { label: "Qwen 3", value: "qwen3" },
104
+ ],
105
+ },
106
+ ],
107
+ maxOutputTokens: 1200,
108
+ promptCaching: true,
109
+ translate: true,
110
+ uploadCollections: {
111
+ aiInput: true,
112
+ generateFields: false,
90
113
  },
91
- {
92
- id: "ollama",
93
- label: "Local Ollama",
94
- provider: "openai",
95
- baseURL: "http://localhost:11434/v1",
96
- apiKey: "ollama",
97
- models: [
98
- { label: "Llama 3.3", value: "llama3.3" },
99
- { label: "Qwen 3", value: "qwen3" },
100
- ],
114
+ maxTokenUsage: {
115
+ type: "user",
116
+ perDay: 50_000,
117
+ perWeek: 250_000,
101
118
  },
102
- ],
103
- maxOutputTokens: 1200,
104
- maxTokenUsage: {
105
- type: "user",
106
- perDay: 50_000,
107
- perWeek: 250_000,
108
- },
109
- };
119
+ }
110
120
  ```
111
121
 
112
122
  `models` configures model choices for the user-selected provider mode. Use `providers` instead when provider selection, credentials, and endpoints should be managed centrally.
@@ -117,7 +127,7 @@ Controls whether the collapsible AI Assistant input is added to collection and g
117
127
 
118
128
  ```ts
119
129
  payloadAiPlugin({
120
- aiInput: false,
130
+ aiInput: false,
121
131
  })
122
132
  ```
123
133
 
@@ -129,12 +139,51 @@ Controls whether supported fields receive a Generate control. It defaults to `tr
129
139
 
130
140
  ```ts
131
141
  payloadAiPlugin({
132
- generateFields: false,
142
+ generateFields: false,
133
143
  })
134
144
  ```
135
145
 
136
146
  Generate controls are added to `text`, `textarea`, `richText`, and `json` fields in collection and global edit views. When disabled, the plugin also omits the `/ai-generate-field` endpoint. The embedded AI Assistant and dashboard remain available.
137
147
 
148
+ ### `authCollections` and `uploadCollections`
149
+
150
+ Auth and upload collections do not receive the embedded AI Assistant or Generate controls by default. Enable either feature explicitly for the respective collection type:
151
+
152
+ ```ts
153
+ payloadAiPlugin({
154
+ authCollections: {
155
+ aiInput: true,
156
+ generateFields: true,
157
+ },
158
+ uploadCollections: {
159
+ aiInput: true,
160
+ generateFields: true,
161
+ },
162
+ })
163
+ ```
164
+
165
+ The global `aiInput` and `generateFields` options still take precedence when set to `false`.
166
+
167
+ ### `translate`
168
+
169
+ Controls document translation for localized collections and globals. It defaults to `true`.
170
+
171
+ ```ts
172
+ payloadAiPlugin({
173
+ translate: false,
174
+ })
175
+ ```
176
+
177
+ When enabled, the plugin adds a `Translate` action to the document header. It is shown only when:
178
+
179
+ - a non-default locale is selected
180
+ - at least one localized field is empty in that locale
181
+ - the corresponding field in the default locale contains source content
182
+
183
+ Translation fills only empty localized fields in the current Payload form and marks it as modified. Existing translated values are never overwritten. It does not save the document or global, so the generated values can be reviewed and validated before saving.
184
+
185
+ When disabled, the plugin does not register the header control or the `/ai-translate-document` endpoint.
186
+
138
187
  ### `collections`
139
188
 
140
189
  Restricts AI read and write proposals to enabled collection slugs. If omitted, all non-internal Payload collections are available.
@@ -143,9 +192,9 @@ Use `true` to enable all AI actions for a collection:
143
192
 
144
193
  ```ts
145
194
  payloadAiPlugin({
146
- collections: {
147
- posts: true,
148
- },
195
+ collections: {
196
+ posts: true,
197
+ },
149
198
  })
150
199
  ```
151
200
 
@@ -153,14 +202,14 @@ Use granular permissions to control each action:
153
202
 
154
203
  ```ts
155
204
  payloadAiPlugin({
156
- collections: {
157
- posts: {
158
- read: true,
159
- create: true,
160
- update: true,
161
- delete: false,
205
+ collections: {
206
+ posts: {
207
+ read: true,
208
+ create: true,
209
+ update: true,
210
+ delete: false,
211
+ },
162
212
  },
163
- },
164
213
  })
165
214
  ```
166
215
 
@@ -168,14 +217,14 @@ You can mix both forms in the same object:
168
217
 
169
218
  ```ts
170
219
  payloadAiPlugin({
171
- collections: {
172
- posts: true,
173
- pages: true,
174
- users: {
175
- read: true,
176
- update: true,
220
+ collections: {
221
+ posts: true,
222
+ pages: true,
223
+ users: {
224
+ read: true,
225
+ update: true,
226
+ },
177
227
  },
178
- },
179
228
  })
180
229
  ```
181
230
 
@@ -187,19 +236,19 @@ Enables media uploads from the AI assistant. Uploaded files are created through
187
236
 
188
237
  ```ts
189
238
  payloadAiPlugin({
190
- collections: {
239
+ collections: {
240
+ media: {
241
+ read: true,
242
+ update: true,
243
+ },
244
+ posts: true,
245
+ },
191
246
  media: {
192
- read: true,
193
- update: true,
247
+ enabled: true,
248
+ collectionSlug: "media",
249
+ acceptedMimeTypes: ["image/*"],
250
+ maxFileSize: 10 * 1024 * 1024,
194
251
  },
195
- posts: true,
196
- },
197
- media: {
198
- enabled: true,
199
- collectionSlug: "media",
200
- acceptedMimeTypes: ["image/*"],
201
- maxFileSize: 10 * 1024 * 1024,
202
- },
203
252
  })
204
253
  ```
205
254
 
@@ -254,7 +303,7 @@ Controls the maximum number of output tokens the chat endpoint may generate per
254
303
 
255
304
  ```ts
256
305
  payloadAiPlugin({
257
- maxOutputTokens: 1200,
306
+ maxOutputTokens: 1200,
258
307
  })
259
308
  ```
260
309
 
@@ -264,11 +313,11 @@ Limits total AI tokens across rolling 24-hour and 7-day windows.
264
313
 
265
314
  ```ts
266
315
  payloadAiPlugin({
267
- maxTokenUsage: {
268
- type: "user",
269
- perDay: 50_000,
270
- perWeek: 250_000,
271
- },
316
+ maxTokenUsage: {
317
+ type: "user",
318
+ perDay: 50_000,
319
+ perWeek: 250_000,
320
+ },
272
321
  })
273
322
  ```
274
323
 
@@ -276,13 +325,25 @@ Use `type: "user"` to enforce separate budgets per authenticated user, or `type:
276
325
 
277
326
  Completed model usage is stored in the hidden `payload-ai-usage` collection. Requests made after a limit is reached return HTTP `429`. Because providers report token usage after completion, the request that crosses a limit is allowed to finish and subsequent requests are blocked.
278
327
 
328
+ ### `promptCaching`
329
+
330
+ Enables provider prompt-caching hints and defaults to `true`. The chat endpoint keeps static instructions and Payload schemas in a stable prompt prefix, adds Anthropic cache-control breakpoints, and supplies a stable OpenAI prompt cache key. Gemini uses the same stable prefix with its implicit caching.
331
+
332
+ ```ts
333
+ payloadAiPlugin({
334
+ promptCaching: false,
335
+ })
336
+ ```
337
+
338
+ Disable it when explicit provider-side caching is not desired. Dynamic document data and user prompts are never placed in the cacheable schema section. Managed providers with a custom `baseURL` receive the stable prefix but no provider-specific cache parameters, preserving compatibility with Ollama, vLLM, and other OpenAI-compatible endpoints.
339
+
279
340
  ### `allowUserApiKeys`
280
341
 
281
342
  Controls whether the plugin adds an `aiApiKey` field to the admin user collection.
282
343
 
283
344
  ```ts
284
345
  payloadAiPlugin({
285
- allowUserApiKeys: false,
346
+ allowUserApiKeys: false,
286
347
  })
287
348
  ```
288
349
 
@@ -322,12 +383,12 @@ The apply endpoint returns only minimal status/doc references and does not retur
322
383
  ## Exports
323
384
 
324
385
  ```ts
325
- import { payloadAiPlugin } from "@mvriu5/payload-ai";
326
- import type { PayloadAiPluginOptions } from "@mvriu5/payload-ai";
386
+ import { payloadAiPlugin } from "@mvriu5/payload-ai"
387
+ import type { PayloadAiPluginOptions } from "@mvriu5/payload-ai"
327
388
  ```
328
389
 
329
390
  Client components are exported through:
330
391
 
331
392
  ```ts
332
- import { AIInput, AIApiKeyField } from "@mvriu5/payload-ai/client";
393
+ import { AIInput, AIApiKeyField } from "@mvriu5/payload-ai/client"
333
394
  ```
@@ -0,0 +1,23 @@
1
+ import type { SystemModelMessage } from "ai";
2
+ import type { AIProvider } from "./providerOptions.js";
3
+ type PromptCacheContext = {
4
+ cacheable: Record<string, unknown>[];
5
+ dynamic: Record<string, unknown>[];
6
+ };
7
+ type PromptCacheProviderOptions = NonNullable<SystemModelMessage["providerOptions"]>;
8
+ export declare const splitPromptCacheContext: (mentionContext: Record<string, unknown>[]) => PromptCacheContext;
9
+ export declare const createCachedSystemMessages: ({ cacheableContext, cacheableInstructions, dynamicInstructions, enabled, provider, staticInstructions, }: {
10
+ cacheableContext: Record<string, unknown>[];
11
+ cacheableInstructions: string[];
12
+ dynamicInstructions: string[];
13
+ enabled: boolean;
14
+ provider: AIProvider;
15
+ staticInstructions: string[];
16
+ }) => SystemModelMessage[];
17
+ export declare const createPromptCacheProviderOptions: ({ cacheKeyParts, enabled, model, provider, }: {
18
+ cacheKeyParts: unknown[];
19
+ enabled: boolean;
20
+ model: string;
21
+ provider: AIProvider;
22
+ }) => PromptCacheProviderOptions | undefined;
23
+ export {};
@@ -0,0 +1,120 @@
1
+ import { createHash } from "node:crypto";
2
+ const getContextKey = (context)=>{
3
+ return [
4
+ context.type,
5
+ context.parent,
6
+ context.collection,
7
+ context.slug
8
+ ].filter(Boolean).join(":") || JSON.stringify(context);
9
+ };
10
+ export const splitPromptCacheContext = (mentionContext)=>{
11
+ const cacheable = [];
12
+ const dynamic = [];
13
+ const cacheableKeys = new Set();
14
+ const addCacheable = (context)=>{
15
+ const key = getContextKey(context);
16
+ if (cacheableKeys.has(key)) return;
17
+ cacheableKeys.add(key);
18
+ cacheable.push(context);
19
+ };
20
+ for (const context of mentionContext){
21
+ if ([
22
+ "block",
23
+ "collection"
24
+ ].includes(String(context.type))) {
25
+ addCacheable(context);
26
+ continue;
27
+ }
28
+ if (context.type === "global" && "doc" in context) {
29
+ const { doc, ...schema } = context;
30
+ addCacheable(schema);
31
+ dynamic.push({
32
+ doc,
33
+ slug: context.slug,
34
+ type: "globalDocument"
35
+ });
36
+ continue;
37
+ }
38
+ if (context.type === "global") {
39
+ addCacheable(context);
40
+ continue;
41
+ }
42
+ if (context.type === "mediaAttachment" && context.schema && typeof context.schema === "object") {
43
+ addCacheable(context.schema);
44
+ const { schema: _schema, ...attachmentContext } = context;
45
+ dynamic.push(attachmentContext);
46
+ continue;
47
+ }
48
+ dynamic.push(context);
49
+ }
50
+ return {
51
+ cacheable,
52
+ dynamic
53
+ };
54
+ };
55
+ const withAnthropicCacheControl = (message, enabled, provider)=>{
56
+ if (!enabled || provider !== "claude") return message;
57
+ return {
58
+ ...message,
59
+ providerOptions: {
60
+ anthropic: {
61
+ cacheControl: {
62
+ type: "ephemeral"
63
+ }
64
+ }
65
+ }
66
+ };
67
+ };
68
+ export const createCachedSystemMessages = ({ cacheableContext, cacheableInstructions, dynamicInstructions, enabled, provider, staticInstructions })=>{
69
+ const messages = [
70
+ withAnthropicCacheControl({
71
+ content: staticInstructions.join("\n"),
72
+ role: "system"
73
+ }, enabled, provider)
74
+ ];
75
+ const scopedContent = [
76
+ ...cacheableContext.length ? [
77
+ "Payload schema references JSON. Use exact field names and values:",
78
+ JSON.stringify(cacheableContext)
79
+ ] : [],
80
+ ...cacheableInstructions
81
+ ];
82
+ if (scopedContent.length > 0) {
83
+ messages.push(withAnthropicCacheControl({
84
+ content: scopedContent.join("\n"),
85
+ role: "system"
86
+ }, enabled, provider));
87
+ }
88
+ if (dynamicInstructions.length > 0) {
89
+ messages.push({
90
+ content: dynamicInstructions.join("\n"),
91
+ role: "system"
92
+ });
93
+ }
94
+ return messages;
95
+ };
96
+ export const createPromptCacheProviderOptions = ({ cacheKeyParts, enabled, model, provider })=>{
97
+ if (!enabled) return undefined;
98
+ if (provider === "openai") {
99
+ const digest = createHash("sha256").update(JSON.stringify([
100
+ "payload-ai-v1",
101
+ model,
102
+ ...cacheKeyParts
103
+ ])).digest("hex").slice(0, 32);
104
+ return {
105
+ openai: {
106
+ promptCacheKey: `payload-ai-${digest}`
107
+ }
108
+ };
109
+ }
110
+ if (provider === "openrouter" && model.toLowerCase().startsWith("anthropic/")) {
111
+ return {
112
+ openrouter: {
113
+ cache_control: {
114
+ type: "ephemeral"
115
+ }
116
+ }
117
+ };
118
+ }
119
+ return undefined;
120
+ };
@@ -0,0 +1,30 @@
1
+ import type { ProposalValidationIssue } from "../payload/proposalData.js";
2
+ export declare const maxProposalRepairAttempts = 1;
3
+ export type CompactProposalRepairIssue = {
4
+ code: string;
5
+ hint: string;
6
+ path: string;
7
+ };
8
+ type ProposalRepairTarget = {
9
+ collection?: string;
10
+ id?: string;
11
+ slug?: string;
12
+ tool: string;
13
+ };
14
+ export declare const compactProposalRepairIssues: (issues: ProposalValidationIssue[]) => CompactProposalRepairIssue[];
15
+ export declare const createProposalRepairKey: ({ collection, id, slug, tool }: ProposalRepairTarget) => string;
16
+ export declare const createProposalRepairTracker: () => {
17
+ beginCall(target: ProposalRepairTarget): "initial" | "blocked" | "repair";
18
+ registerFailure(target: ProposalRepairTarget): {
19
+ attempt: number;
20
+ errorCode: "REPAIR_EXHAUSTED";
21
+ maxAttempts: number;
22
+ retryable: boolean;
23
+ } | {
24
+ attempt: number;
25
+ errorCode: "INVALID_PROPOSAL_DATA";
26
+ maxAttempts: number;
27
+ retryable: boolean;
28
+ };
29
+ };
30
+ export {};
@@ -0,0 +1,76 @@
1
+ export const maxProposalRepairAttempts = 1;
2
+ const getIssueHint = (issue)=>{
3
+ switch(issue.code){
4
+ case "invalid_array":
5
+ return "Use an array of complete objects matching the child fields.";
6
+ case "invalid_block_type":
7
+ return "Use an exact blockType from the schema.";
8
+ case "invalid_blocks":
9
+ return "Use an array of block objects with blockType and exact field names.";
10
+ case "invalid_checkbox":
11
+ return "Use true or false.";
12
+ case "invalid_container":
13
+ return "Use an object matching the nested field schema.";
14
+ case "invalid_date":
15
+ return "Use a valid date string.";
16
+ case "invalid_option":
17
+ return issue.message;
18
+ case "invalid_relationship":
19
+ return "Use an existing document ID or { relationTo, value } for polymorphic relationships.";
20
+ case "missing_required_field":
21
+ return "Add the required field with a schema-compatible value.";
22
+ case "non_localized_field_in_secondary_locale":
23
+ return "Move this field to the primary locale data.";
24
+ case "unknown_field":
25
+ return "Remove it and use exact schema field names only.";
26
+ default:
27
+ return issue.message;
28
+ }
29
+ };
30
+ export const compactProposalRepairIssues = (issues)=>{
31
+ return issues.slice(0, 6).map((issue)=>({
32
+ code: issue.code,
33
+ hint: getIssueHint(issue).slice(0, 180),
34
+ path: issue.path
35
+ }));
36
+ };
37
+ export const createProposalRepairKey = ({ collection, id, slug, tool })=>{
38
+ return [
39
+ tool,
40
+ collection || slug || "",
41
+ id || ""
42
+ ].join(":");
43
+ };
44
+ export const createProposalRepairTracker = ()=>{
45
+ const statesByTarget = new Map();
46
+ return {
47
+ beginCall (target) {
48
+ const state = statesByTarget.get(createProposalRepairKey(target));
49
+ if (!state) return "initial";
50
+ if (state.repairCallConsumed) return "blocked";
51
+ state.repairCallConsumed = true;
52
+ return "repair";
53
+ },
54
+ registerFailure (target) {
55
+ const key = createProposalRepairKey(target);
56
+ const state = statesByTarget.get(key);
57
+ if (state) {
58
+ return {
59
+ attempt: maxProposalRepairAttempts,
60
+ errorCode: "REPAIR_EXHAUSTED",
61
+ maxAttempts: maxProposalRepairAttempts,
62
+ retryable: false
63
+ };
64
+ }
65
+ statesByTarget.set(key, {
66
+ repairCallConsumed: false
67
+ });
68
+ return {
69
+ attempt: 1,
70
+ errorCode: "INVALID_PROPOSAL_DATA",
71
+ maxAttempts: maxProposalRepairAttempts,
72
+ retryable: true
73
+ };
74
+ }
75
+ };
76
+ };
@@ -1,2 +1,3 @@
1
+ export declare const isSensitiveKey: (key: string) => boolean;
1
2
  export declare const containsSensitiveData: (value: unknown) => boolean;
2
3
  export declare const redactSensitiveData: (value: unknown) => unknown;
@@ -5,10 +5,12 @@ const sensitiveKeyPatterns = [
5
5
  /^aiApiKey$/i,
6
6
  /^authorization$/i,
7
7
  /^accessToken$/i,
8
+ /^password$/i,
9
+ /^privateKey$/i,
8
10
  /^refreshToken$/i,
9
11
  /^secret$/i
10
12
  ];
11
- const isSensitiveKey = (key)=>sensitiveKeyPatterns.some((pattern)=>pattern.test(key));
13
+ export const isSensitiveKey = (key)=>sensitiveKeyPatterns.some((pattern)=>pattern.test(key));
12
14
  export const containsSensitiveData = (value)=>{
13
15
  if (Array.isArray(value)) return value.some(containsSensitiveData);
14
16
  if (!isRecord(value)) return false;
@@ -1,5 +1,5 @@
1
1
  type AIInputProps = {
2
2
  isDashboard?: boolean;
3
3
  };
4
- declare const AIInput: ({ isDashboard }: AIInputProps) => import("react").JSX.Element;
4
+ declare const AIInput: ({ isDashboard }: AIInputProps) => import("react").JSX.Element | null;
5
5
  export default AIInput;
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Button, ChevronIcon, useConfig, useDocumentForm, useDocumentInfo, useLocale } from "@payloadcms/ui";
3
+ import { Button, ChevronIcon, useAuth, useConfig, useDocumentForm, useDocumentInfo, useLocale } from "@payloadcms/ui";
4
4
  import { formatAdminURL } from "payload/shared";
5
5
  import { useEffect, useRef, useState } from "react";
6
6
  import { mergeData } from "../../payload/shared.js";
@@ -540,6 +540,8 @@ const DocumentAIInput = ()=>{
540
540
  });
541
541
  };
542
542
  const AIInput = ({ isDashboard = false })=>{
543
+ const { user } = useAuth();
544
+ if (!user) return null;
543
545
  if (isDashboard) return /*#__PURE__*/ _jsx(AIInputCore, {
544
546
  isDashboard: true
545
547
  });
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useConfig, useDocumentForm, useDocumentInfo, useField, useLocale } from "@payloadcms/ui";
3
+ import { useAuth, useConfig, useDocumentForm, useDocumentInfo, useField, useLocale } from "@payloadcms/ui";
4
4
  import { formatAdminURL } from "payload/shared";
5
5
  import { useState } from "react";
6
6
  import { createGeneratedRichTextValue } from "./richText.js";
@@ -27,6 +27,7 @@ const requestGeneratedValue = ({ apiRoute, body, cacheKey })=>{
27
27
  return request;
28
28
  };
29
29
  const GenerateField = ({ field, generationFieldKey, generationFieldType, path, readOnly })=>{
30
+ const { user } = useAuth();
30
31
  const configContext = useConfig();
31
32
  const documentForm = useDocumentForm();
32
33
  const documentInfo = useDocumentInfo();
@@ -87,7 +88,7 @@ const GenerateField = ({ field, generationFieldKey, generationFieldType, path, r
87
88
  setIsGenerating(false);
88
89
  }
89
90
  };
90
- if (!scope) return null;
91
+ if (!user || !scope) return null;
91
92
  return /*#__PURE__*/ _jsxs("div", {
92
93
  className: styles.generateField,
93
94
  children: [