payload-forgeai 0.2.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 +415 -0
- package/dist/components/BeforeDashboardClient.d.ts +1 -0
- package/dist/components/BeforeDashboardServer.d.ts +2 -0
- package/dist/components/BeforeDashboardServer.module.css +5 -0
- package/dist/components/ForgeAIField.d.ts +2 -0
- package/dist/components/ForgeAIField.js +164 -0
- package/dist/components/ForgeAIField.js.map +1 -0
- package/dist/endpoints/customEndpointHandler.d.ts +2 -0
- package/dist/endpoints/customEndpointHandler.js +7 -0
- package/dist/endpoints/customEndpointHandler.js.map +1 -0
- package/dist/exports/ForgeAIField.d.ts +1 -0
- package/dist/exports/ForgeAIField.js +3 -0
- package/dist/exports/ForgeAIField.js.map +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +309 -0
- package/dist/index.js.map +1 -0
- package/package.json +107 -0
package/README.md
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# Payload Forge AI
|
|
2
|
+
|
|
3
|
+
AI-powered field generation plugin for [Payload CMS](https://payloadcms.com). Automatically generate field content using OpenAI, with support for context-aware generation based on other fields in your document.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🤖 AI-powered field generation using OpenAI
|
|
8
|
+
- 🔗 Context-aware generation using other field values
|
|
9
|
+
- 📝 Custom prompts per field
|
|
10
|
+
- 🎯 Support for relationships, selects, and all field types
|
|
11
|
+
- ⚡ Simple integration with existing Payload collections
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install payload-forge-ai
|
|
17
|
+
# or
|
|
18
|
+
pnpm add payload-forge-ai
|
|
19
|
+
# or
|
|
20
|
+
yarn add payload-forge-ai
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
Add the plugin to your `payload.config.ts`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { payloadForgeAi } from 'payload-forge-ai'
|
|
29
|
+
|
|
30
|
+
export default buildConfig({
|
|
31
|
+
plugins: [
|
|
32
|
+
payloadForgeAi({
|
|
33
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
34
|
+
collections: {
|
|
35
|
+
posts: {
|
|
36
|
+
fields: [
|
|
37
|
+
{
|
|
38
|
+
name: 'title',
|
|
39
|
+
prompt: 'Write a compelling title for this blog post',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'content',
|
|
43
|
+
context: ['title', 'category'],
|
|
44
|
+
prompt: 'Write engaging content for this blog post',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Basic Field Generation
|
|
57
|
+
|
|
58
|
+
Configure a field for AI generation:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
{
|
|
62
|
+
name: 'title',
|
|
63
|
+
prompt: 'Write a catchy title that is max 60 characters',
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The plugin automatically includes the current field value in the context, instructing the AI to generate something different when regenerating. This is useful for getting variations.
|
|
68
|
+
|
|
69
|
+
To disable this behavior and allow the AI to consider the current value without the "generate different" instruction:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
{
|
|
73
|
+
name: 'title',
|
|
74
|
+
prompt: 'Write a catchy title that is max 60 characters',
|
|
75
|
+
generateDifferent: false, // AI can generate similar content
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Context-Aware Generation
|
|
80
|
+
|
|
81
|
+
Use other fields as context for more relevant AI generation:
|
|
82
|
+
|
|
83
|
+
#### Simple Context (by field name)
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
{
|
|
87
|
+
name: 'content',
|
|
88
|
+
context: ['title', 'category'],
|
|
89
|
+
prompt: 'Write the main content for this article',
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Custom Context Prompts
|
|
94
|
+
|
|
95
|
+
Provide custom prompts for how each context field should be used:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
{
|
|
99
|
+
name: 'content',
|
|
100
|
+
context: [
|
|
101
|
+
{
|
|
102
|
+
field: 'title',
|
|
103
|
+
prompt: 'The article title is',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
field: 'category',
|
|
107
|
+
prompt: 'The category for this post is',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
field: 'author',
|
|
111
|
+
prompt: 'Written by author',
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
prompt: 'Write engaging content based on the context above',
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Working with Relationships
|
|
119
|
+
|
|
120
|
+
The plugin automatically fetches and populates relationship fields:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// In your collection
|
|
124
|
+
fields: [
|
|
125
|
+
{
|
|
126
|
+
name: 'author',
|
|
127
|
+
type: 'relationship',
|
|
128
|
+
relationTo: 'users',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'bio',
|
|
132
|
+
type: 'textarea',
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
// In your AI config - Use full author object
|
|
137
|
+
{
|
|
138
|
+
name: 'bio',
|
|
139
|
+
context: [
|
|
140
|
+
{
|
|
141
|
+
field: 'author',
|
|
142
|
+
prompt: 'Author details',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
prompt: 'Write a professional bio',
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Or extract specific fields from the relationship
|
|
149
|
+
{
|
|
150
|
+
name: 'bio',
|
|
151
|
+
context: [
|
|
152
|
+
{
|
|
153
|
+
field: 'author',
|
|
154
|
+
fields: ['name', 'email'], // Only include name and email
|
|
155
|
+
prompt: 'Author details',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
prompt: 'Write a professional bio and mention the author name',
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Use nested prompts - BOTH outer and inner prompts are used
|
|
162
|
+
{
|
|
163
|
+
name: 'content',
|
|
164
|
+
context: [
|
|
165
|
+
{
|
|
166
|
+
field: 'author',
|
|
167
|
+
prompt: 'The author information', // Outer prompt - used as header
|
|
168
|
+
fields: [
|
|
169
|
+
{
|
|
170
|
+
field: 'name',
|
|
171
|
+
prompt: 'Written by', // Inner prompt - used for this specific field
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
field: 'email',
|
|
175
|
+
prompt: 'Contact', // Inner prompt - used for this specific field
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
prompt: 'Write content with author attribution',
|
|
181
|
+
}
|
|
182
|
+
// AI receives:
|
|
183
|
+
// The author information:
|
|
184
|
+
// Written by: John Doe
|
|
185
|
+
// Contact: john@example.com
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Selecting Specific Fields
|
|
189
|
+
|
|
190
|
+
Use the `fields` array to extract only specific properties from context:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
{
|
|
194
|
+
name: 'summary',
|
|
195
|
+
context: [
|
|
196
|
+
{
|
|
197
|
+
field: 'author',
|
|
198
|
+
fields: ['name'], // Only include the author's name
|
|
199
|
+
prompt: 'Written by',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
field: 'metadata',
|
|
203
|
+
fields: ['tags', 'category'], // Extract specific nested properties
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
prompt: 'Write a summary with author attribution',
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Nested Field Selection
|
|
211
|
+
|
|
212
|
+
For complex nested objects, you can use recursive field configuration with prompts at each level:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
{
|
|
216
|
+
name: 'description',
|
|
217
|
+
context: [
|
|
218
|
+
{
|
|
219
|
+
field: 'product',
|
|
220
|
+
prompt: 'Product details', // Outer prompt (used as fallback for nested fields without prompts)
|
|
221
|
+
fields: [
|
|
222
|
+
'name', // Will use: "Product details - name: value"
|
|
223
|
+
'price',
|
|
224
|
+
{
|
|
225
|
+
field: 'manufacturer',
|
|
226
|
+
prompt: 'Made by', // This prompt overrides the parent
|
|
227
|
+
fields: ['name', 'country'], // Will use: "Made by - name: value" and "Made by - country: value"
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
field: 'specs',
|
|
231
|
+
fields: [
|
|
232
|
+
{
|
|
233
|
+
field: 'weight',
|
|
234
|
+
prompt: 'Product weight', // Custom prompt for this specific field
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
prompt: 'Write a product description',
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**How nested prompts work:**
|
|
246
|
+
- **ALL prompts are used** - both outer and nested prompts are included in the context
|
|
247
|
+
- Outer prompts act as section headers/introductions
|
|
248
|
+
- Nested prompts describe individual fields
|
|
249
|
+
- Example output:
|
|
250
|
+
```
|
|
251
|
+
Product details:
|
|
252
|
+
name: Widget X
|
|
253
|
+
Made by:
|
|
254
|
+
name: ACME Corp
|
|
255
|
+
country: USA
|
|
256
|
+
Product weight: 2.5kg
|
|
257
|
+
```
|
|
258
|
+
- This allows fine-grained control over how each piece of data is presented to the AI
|
|
259
|
+
|
|
260
|
+
## Configuration Options
|
|
261
|
+
|
|
262
|
+
### Plugin Options
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
type PayloadForgeAiConfig = {
|
|
266
|
+
apiKey: string // Your OpenAI API key
|
|
267
|
+
disabled?: boolean // Disable the plugin
|
|
268
|
+
collections?: {
|
|
269
|
+
[collectionSlug: string]: {
|
|
270
|
+
fields: PayloadForgeAiFieldConfig[]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Field Configuration
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
type PayloadForgeAiContextField =
|
|
280
|
+
| string // Simple field name
|
|
281
|
+
| {
|
|
282
|
+
field: string // Field name to use as context
|
|
283
|
+
prompt?: string // Custom prompt for this context field
|
|
284
|
+
fields?: PayloadForgeAiContextField[] // Recursively extract nested fields
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
type PayloadForgeAiFieldConfig = {
|
|
288
|
+
name: string // Field name to enable AI generation
|
|
289
|
+
prompt: string // Instructions for the AI
|
|
290
|
+
context?: PayloadForgeAiContextField[] // Fields to use as context
|
|
291
|
+
generateDifferent?: boolean // If true (default), instructs AI to generate different from current value
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Examples
|
|
296
|
+
|
|
297
|
+
### Blog Post with Category and Author Context
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
payloadForgeAi({
|
|
301
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
302
|
+
collections: {
|
|
303
|
+
posts: {
|
|
304
|
+
fields: [
|
|
305
|
+
{
|
|
306
|
+
name: 'title',
|
|
307
|
+
context: ['category'],
|
|
308
|
+
prompt: 'Write a title (max 8 words)',
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: 'excerpt',
|
|
312
|
+
context: ['title', 'category'],
|
|
313
|
+
prompt: 'Write a brief excerpt (max 150 characters)',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: 'content',
|
|
317
|
+
context: [
|
|
318
|
+
{ field: 'title', prompt: 'Article title' },
|
|
319
|
+
{ field: 'category', prompt: 'Category' },
|
|
320
|
+
{ field: 'excerpt', prompt: 'Summary' },
|
|
321
|
+
{
|
|
322
|
+
field: 'author',
|
|
323
|
+
fields: ['name'], // Only use author's name
|
|
324
|
+
prompt: 'Written by'
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
prompt: 'Write comprehensive content (300-500 words) and include author attribution at the end',
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
})
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Product Description with Related Data
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
payloadForgeAi({
|
|
339
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
340
|
+
collections: {
|
|
341
|
+
products: {
|
|
342
|
+
fields: [
|
|
343
|
+
{
|
|
344
|
+
name: 'description',
|
|
345
|
+
context: [
|
|
346
|
+
{ field: 'name', prompt: 'Product name' },
|
|
347
|
+
{ field: 'category', prompt: 'Category' },
|
|
348
|
+
{ field: 'price', prompt: 'Price' },
|
|
349
|
+
{ field: 'features', prompt: 'Key features' },
|
|
350
|
+
],
|
|
351
|
+
prompt: 'Write a compelling product description',
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: 'seoTitle',
|
|
355
|
+
context: ['name', 'category'],
|
|
356
|
+
prompt: 'Write an SEO-optimized title (max 60 chars)',
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'seoDescription',
|
|
360
|
+
context: ['name', 'description'],
|
|
361
|
+
prompt: 'Write an SEO meta description (max 160 chars)',
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
})
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Environment Variables
|
|
370
|
+
|
|
371
|
+
Create a `.env` file in your project:
|
|
372
|
+
|
|
373
|
+
```env
|
|
374
|
+
OPENAI_API_KEY=your_openai_api_key_here
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## How It Works
|
|
378
|
+
|
|
379
|
+
1. The plugin adds a "Generate" button to configured fields in the Payload admin UI
|
|
380
|
+
2. When clicked, it gathers context from other fields in the document
|
|
381
|
+
3. Includes the current field value (if any) and instructs the AI to generate something different (configurable)
|
|
382
|
+
4. Sends the context and prompt to OpenAI via your API endpoint
|
|
383
|
+
5. Returns the generated content and populates the field
|
|
384
|
+
|
|
385
|
+
## Development
|
|
386
|
+
|
|
387
|
+
### Running the Dev Environment
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
# Install dependencies
|
|
391
|
+
pnpm install
|
|
392
|
+
|
|
393
|
+
# Create .env file
|
|
394
|
+
cp .env.example .env
|
|
395
|
+
|
|
396
|
+
# Add your OpenAI API key to .env
|
|
397
|
+
# OPENAI_API_KEY=your_key_here
|
|
398
|
+
|
|
399
|
+
# Start the dev server
|
|
400
|
+
pnpm dev
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Testing
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
pnpm test
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## License
|
|
410
|
+
|
|
411
|
+
MIT
|
|
412
|
+
|
|
413
|
+
## Support
|
|
414
|
+
|
|
415
|
+
For issues and questions, please open an issue on GitHub.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const BeforeDashboardClient: () => import("react").JSX.Element;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
4
|
+
import { useConfig, useDocumentInfo, useField, FieldLabel, useFormFields } from '@payloadcms/ui';
|
|
5
|
+
export const ForgeAIField = (props)=>{
|
|
6
|
+
const { path, field, readOnly } = props;
|
|
7
|
+
const { setValue, value, errorMessage, showError, formProcessing } = useField({
|
|
8
|
+
path
|
|
9
|
+
});
|
|
10
|
+
const { collectionSlug, id: docId } = useDocumentInfo();
|
|
11
|
+
const { config } = useConfig();
|
|
12
|
+
// Access all form fields for context
|
|
13
|
+
const allFields = useFormFields(([fields])=>fields);
|
|
14
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
15
|
+
const [genError, setGenError] = useState(null);
|
|
16
|
+
const endpoint = useMemo(()=>{
|
|
17
|
+
if (!collectionSlug) return null;
|
|
18
|
+
const base = config?.serverURL || '';
|
|
19
|
+
return `${base}/api/forge-ai/${collectionSlug}/${path}`;
|
|
20
|
+
}, [
|
|
21
|
+
collectionSlug,
|
|
22
|
+
config?.serverURL,
|
|
23
|
+
path
|
|
24
|
+
]);
|
|
25
|
+
const generate = useCallback(async ()=>{
|
|
26
|
+
if (!endpoint) return;
|
|
27
|
+
setIsGenerating(true);
|
|
28
|
+
setGenError(null);
|
|
29
|
+
try {
|
|
30
|
+
// Gather context from form fields
|
|
31
|
+
const contextData = {};
|
|
32
|
+
if (allFields) {
|
|
33
|
+
Object.entries(allFields).forEach(([fieldPath, fieldState])=>{
|
|
34
|
+
contextData[fieldPath] = fieldState.value;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const res = await fetch(endpoint, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
credentials: 'include',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json'
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
context: contextData,
|
|
45
|
+
documentId: docId,
|
|
46
|
+
currentValue: value
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) throw new Error(await res.text());
|
|
50
|
+
const generated = await res.json();
|
|
51
|
+
setValue(generated);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
setGenError(e instanceof Error ? e.message : String(e));
|
|
54
|
+
} finally{
|
|
55
|
+
setIsGenerating(false);
|
|
56
|
+
}
|
|
57
|
+
}, [
|
|
58
|
+
endpoint,
|
|
59
|
+
setValue,
|
|
60
|
+
allFields,
|
|
61
|
+
docId
|
|
62
|
+
]);
|
|
63
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
64
|
+
className: "field-type",
|
|
65
|
+
children: [
|
|
66
|
+
/*#__PURE__*/ _jsx(FieldLabel, {
|
|
67
|
+
htmlFor: path,
|
|
68
|
+
label: field.label ?? field.name,
|
|
69
|
+
required: field.required
|
|
70
|
+
}),
|
|
71
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
72
|
+
children: [
|
|
73
|
+
/*#__PURE__*/ _jsx("input", {
|
|
74
|
+
id: path,
|
|
75
|
+
style: {
|
|
76
|
+
width: '100%'
|
|
77
|
+
},
|
|
78
|
+
disabled: readOnly || formProcessing,
|
|
79
|
+
value: value ?? '',
|
|
80
|
+
onChange: (e)=>setValue(e.target.value)
|
|
81
|
+
}),
|
|
82
|
+
/*#__PURE__*/ _jsxs("button", {
|
|
83
|
+
type: "button",
|
|
84
|
+
onClick: generate,
|
|
85
|
+
disabled: readOnly || formProcessing || isGenerating || !endpoint,
|
|
86
|
+
style: {
|
|
87
|
+
marginTop: 8,
|
|
88
|
+
padding: '6px 12px',
|
|
89
|
+
border: '1px solid var(--theme-elevation-400)',
|
|
90
|
+
borderRadius: 4,
|
|
91
|
+
background: 'var(--theme-elevation-50)',
|
|
92
|
+
cursor: readOnly || formProcessing || isGenerating || !endpoint ? 'not-allowed' : 'pointer',
|
|
93
|
+
display: 'inline-flex',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
gap: 6,
|
|
96
|
+
fontSize: 13,
|
|
97
|
+
color: 'var(--theme-elevation-800)',
|
|
98
|
+
transition: 'all 0.2s ease',
|
|
99
|
+
opacity: readOnly || formProcessing || !endpoint ? 0.5 : 1
|
|
100
|
+
},
|
|
101
|
+
onMouseEnter: (e)=>{
|
|
102
|
+
if (!readOnly && !formProcessing && !isGenerating && endpoint) {
|
|
103
|
+
e.currentTarget.style.background = 'var(--theme-elevation-100)';
|
|
104
|
+
e.currentTarget.style.borderColor = 'var(--theme-elevation-500)';
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
onMouseLeave: (e)=>{
|
|
108
|
+
e.currentTarget.style.background = 'var(--theme-elevation-50)';
|
|
109
|
+
e.currentTarget.style.borderColor = 'var(--theme-elevation-400)';
|
|
110
|
+
},
|
|
111
|
+
children: [
|
|
112
|
+
/*#__PURE__*/ _jsx("svg", {
|
|
113
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
114
|
+
width: "16",
|
|
115
|
+
height: "16",
|
|
116
|
+
viewBox: "0 0 24 24",
|
|
117
|
+
fill: "none",
|
|
118
|
+
stroke: "currentColor",
|
|
119
|
+
strokeWidth: "2",
|
|
120
|
+
strokeLinecap: "round",
|
|
121
|
+
strokeLinejoin: "round",
|
|
122
|
+
style: {
|
|
123
|
+
animation: isGenerating ? 'spin 1s linear infinite' : 'none'
|
|
124
|
+
},
|
|
125
|
+
children: /*#__PURE__*/ _jsx("path", {
|
|
126
|
+
d: "M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"
|
|
127
|
+
})
|
|
128
|
+
}),
|
|
129
|
+
isGenerating ? 'Generating…' : 'Generate with AI'
|
|
130
|
+
]
|
|
131
|
+
}),
|
|
132
|
+
/*#__PURE__*/ _jsx("style", {
|
|
133
|
+
children: `
|
|
134
|
+
@keyframes spin {
|
|
135
|
+
from {
|
|
136
|
+
transform: rotate(0deg);
|
|
137
|
+
}
|
|
138
|
+
to {
|
|
139
|
+
transform: rotate(360deg);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
`
|
|
143
|
+
})
|
|
144
|
+
]
|
|
145
|
+
}),
|
|
146
|
+
genError ? /*#__PURE__*/ _jsx("div", {
|
|
147
|
+
style: {
|
|
148
|
+
marginTop: 6,
|
|
149
|
+
color: 'var(--theme-error-500)'
|
|
150
|
+
},
|
|
151
|
+
children: genError
|
|
152
|
+
}) : null,
|
|
153
|
+
showError && errorMessage ? /*#__PURE__*/ _jsx("div", {
|
|
154
|
+
style: {
|
|
155
|
+
marginTop: 6,
|
|
156
|
+
color: 'var(--theme-error-500)'
|
|
157
|
+
},
|
|
158
|
+
children: errorMessage
|
|
159
|
+
}) : null
|
|
160
|
+
]
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
//# sourceMappingURL=ForgeAIField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/ForgeAIField.tsx"],"sourcesContent":["'use client'\n\nimport React, { useCallback, useMemo, useState } from 'react'\nimport { type TextFieldClientComponent } from 'payload'\nimport { useConfig, useDocumentInfo, useField, FieldLabel, useFormFields } from '@payloadcms/ui'\n\nexport const ForgeAIField: TextFieldClientComponent = (props) => {\n const { path, field, readOnly } = props\n\n const { setValue, value, errorMessage, showError, formProcessing } = useField<string>({ path })\n const { collectionSlug, id: docId } = useDocumentInfo()\n const { config } = useConfig()\n \n // Access all form fields for context\n const allFields = useFormFields(([fields]) => fields)\n\n const [isGenerating, setIsGenerating] = useState(false)\n const [genError, setGenError] = useState<string | null>(null)\n\n const endpoint = useMemo(() => {\n if (!collectionSlug) return null\n const base = config?.serverURL || ''\n return `${base}/api/forge-ai/${collectionSlug}/${path}`\n }, [collectionSlug, config?.serverURL, path])\n\n const generate = useCallback(async () => {\n if (!endpoint) return\n setIsGenerating(true)\n setGenError(null)\n try {\n // Gather context from form fields\n const contextData: Record<string, any> = {}\n if (allFields) {\n Object.entries(allFields).forEach(([fieldPath, fieldState]) => {\n contextData[fieldPath] = fieldState.value\n })\n }\n\n const res = await fetch(endpoint, {\n method: 'POST',\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n context: contextData,\n documentId: docId,\n currentValue: value, // Include current value so AI can generate something different\n }),\n })\n\n if (!res.ok) throw new Error(await res.text())\n\n const generated = await res.json()\n setValue(generated)\n } catch (e) {\n setGenError(e instanceof Error ? e.message : String(e))\n } finally {\n setIsGenerating(false)\n }\n }, [endpoint, setValue, allFields, docId])\n\n return (\n <div className=\"field-type\">\n <FieldLabel htmlFor={path} label={field.label ?? field.name} required={field.required} />\n\n <div>\n <input\n id={path}\n style={{ width: '100%' }}\n disabled={readOnly || formProcessing}\n value={value ?? ''}\n onChange={(e) => setValue(e.target.value)}\n />\n\n <button\n type=\"button\"\n onClick={generate}\n disabled={readOnly || formProcessing || isGenerating || !endpoint}\n style={{\n marginTop: 8,\n padding: '6px 12px',\n border: '1px solid var(--theme-elevation-400)',\n borderRadius: 4,\n background: 'var(--theme-elevation-50)',\n cursor: readOnly || formProcessing || isGenerating || !endpoint ? 'not-allowed' : 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n gap: 6,\n fontSize: 13,\n color: 'var(--theme-elevation-800)',\n transition: 'all 0.2s ease',\n opacity: readOnly || formProcessing || !endpoint ? 0.5 : 1,\n }}\n onMouseEnter={(e) => {\n if (!readOnly && !formProcessing && !isGenerating && endpoint) {\n e.currentTarget.style.background = 'var(--theme-elevation-100)'\n e.currentTarget.style.borderColor = 'var(--theme-elevation-500)'\n }\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = 'var(--theme-elevation-50)'\n e.currentTarget.style.borderColor = 'var(--theme-elevation-400)'\n }}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n style={{\n animation: isGenerating ? 'spin 1s linear infinite' : 'none',\n }}\n >\n <path d=\"M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2\" />\n </svg>\n {isGenerating ? 'Generating…' : 'Generate with AI'}\n </button>\n\n <style>{`\n @keyframes spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n }\n `}</style>\n </div>\n\n {genError ? (\n <div style={{ marginTop: 6, color: 'var(--theme-error-500)' }}>{genError}</div>\n ) : null}\n {showError && errorMessage ? (\n <div style={{ marginTop: 6, color: 'var(--theme-error-500)' }}>{errorMessage}</div>\n ) : null}\n </div>\n )\n}\n"],"names":["React","useCallback","useMemo","useState","useConfig","useDocumentInfo","useField","FieldLabel","useFormFields","ForgeAIField","props","path","field","readOnly","setValue","value","errorMessage","showError","formProcessing","collectionSlug","id","docId","config","allFields","fields","isGenerating","setIsGenerating","genError","setGenError","endpoint","base","serverURL","generate","contextData","Object","entries","forEach","fieldPath","fieldState","res","fetch","method","credentials","headers","body","JSON","stringify","context","documentId","currentValue","ok","Error","text","generated","json","e","message","String","div","className","htmlFor","label","name","required","input","style","width","disabled","onChange","target","button","type","onClick","marginTop","padding","border","borderRadius","background","cursor","display","alignItems","gap","fontSize","color","transition","opacity","onMouseEnter","currentTarget","borderColor","onMouseLeave","svg","xmlns","height","viewBox","fill","stroke","strokeWidth","strokeLinecap","strokeLinejoin","animation","d"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/customEndpointHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\n\nexport const customEndpointHandler: PayloadHandler = () => {\n return Response.json({ message: 'Hello from custom endpoint' })\n}\n"],"names":["customEndpointHandler","Response","json","message"],"mappings":"AAEA,OAAO,MAAMA,wBAAwC;IACnD,OAAOC,SAASC,IAAI,CAAC;QAAEC,SAAS;IAA6B;AAC/D,EAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ForgeAIField } from '../components/ForgeAIField.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/exports/ForgeAIField.ts"],"sourcesContent":["export { ForgeAIField } from '../components/ForgeAIField.js'\n"],"names":["ForgeAIField"],"mappings":"AAAA,SAASA,YAAY,QAAQ,gCAA+B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BeforeDashboardClient } from '../components/BeforeDashboardClient.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BeforeDashboardServer } from '../components/BeforeDashboardServer.js';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CollectionSlug, Config, Field } from 'payload';
|
|
2
|
+
export type PayloadForgeAiContextField = string | {
|
|
3
|
+
field: string;
|
|
4
|
+
prompt?: string;
|
|
5
|
+
/**
|
|
6
|
+
* For relationship fields, specify which fields to extract from the related document.
|
|
7
|
+
* For other fields, specify which nested properties to extract.
|
|
8
|
+
* If not specified, the entire value will be used.
|
|
9
|
+
* Can be nested for complex object structures.
|
|
10
|
+
*/
|
|
11
|
+
fields?: PayloadForgeAiContextField[];
|
|
12
|
+
};
|
|
13
|
+
export type PayloadForgeAiFieldConfig = {
|
|
14
|
+
context?: PayloadForgeAiContextField[];
|
|
15
|
+
/**
|
|
16
|
+
* If true, tells the AI to generate something different from the current value.
|
|
17
|
+
* Defaults to true.
|
|
18
|
+
*/
|
|
19
|
+
generateDifferent?: boolean;
|
|
20
|
+
name: string;
|
|
21
|
+
prompt: string;
|
|
22
|
+
};
|
|
23
|
+
export type PayloadForgeAiCollectionConfig = {
|
|
24
|
+
fields: PayloadForgeAiFieldConfig[];
|
|
25
|
+
};
|
|
26
|
+
export type PayloadForgeAiConfig = {
|
|
27
|
+
apiKey: string;
|
|
28
|
+
collections?: Partial<Record<CollectionSlug, PayloadForgeAiCollectionConfig>>;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
};
|
|
31
|
+
export declare const payloadForgeAi: (pluginOptions: PayloadForgeAiConfig) => (config: Config) => Config;
|
|
32
|
+
export declare function setAdminFieldComponent<T extends Field>(field: T, fieldComponent: string): T;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
2
|
+
import { generateText } from 'ai';
|
|
3
|
+
import { flattenTopLevelFields, InvalidConfiguration } from 'payload';
|
|
4
|
+
function getValidFieldNames(collection) {
|
|
5
|
+
const flattened = flattenTopLevelFields(collection.fields);
|
|
6
|
+
return new Set(flattened.map((f)=>f.name).filter((name)=>name != null));
|
|
7
|
+
}
|
|
8
|
+
function validateCollectionFields(collectionSlug, collection, allowedFields) {
|
|
9
|
+
const validNames = getValidFieldNames(collection);
|
|
10
|
+
const invalid = allowedFields.filter((name)=>!validNames.has(name));
|
|
11
|
+
if (invalid.length > 0) {
|
|
12
|
+
throw new InvalidConfiguration(`payload-forge-ai: Collection "${collectionSlug}" does not have these fields: ${invalid.join(', ')}. ` + `Valid top-level fields: ${[
|
|
13
|
+
...validNames
|
|
14
|
+
].sort().join(', ')}.`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Recursively extract fields from an object based on field configuration
|
|
19
|
+
*/ function extractFields(data, fieldConfigs) {
|
|
20
|
+
if (!data || typeof data !== 'object') {
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
const result = {};
|
|
24
|
+
for (const config of fieldConfigs){
|
|
25
|
+
const fieldName = typeof config === 'string' ? config : config.field;
|
|
26
|
+
const nestedFields = typeof config === 'string' ? undefined : config.fields;
|
|
27
|
+
if (data[fieldName] !== undefined) {
|
|
28
|
+
if (nestedFields && nestedFields.length > 0) {
|
|
29
|
+
// Recursively extract nested fields
|
|
30
|
+
result[fieldName] = extractFields(data[fieldName], nestedFields);
|
|
31
|
+
} else {
|
|
32
|
+
// Just include the field value
|
|
33
|
+
result[fieldName] = data[fieldName];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Recursively build context parts with prompts for nested fields
|
|
41
|
+
*/ function buildContextParts(data, fieldConfigs, indent = ' ') {
|
|
42
|
+
if (!data || typeof data !== 'object') {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const parts = [];
|
|
46
|
+
for (const config of fieldConfigs){
|
|
47
|
+
const fieldName = typeof config === 'string' ? config : config.field;
|
|
48
|
+
const customPrompt = typeof config === 'string' ? undefined : config.prompt;
|
|
49
|
+
const nestedFields = typeof config === 'string' ? undefined : config.fields;
|
|
50
|
+
const fieldValue = data[fieldName];
|
|
51
|
+
if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
|
|
52
|
+
if (nestedFields && nestedFields.length > 0 && typeof fieldValue === 'object') {
|
|
53
|
+
// Handle nested fields with their own prompts
|
|
54
|
+
// Add the current field's prompt as a header if it exists
|
|
55
|
+
if (customPrompt) {
|
|
56
|
+
parts.push(`${indent}${customPrompt}:`);
|
|
57
|
+
}
|
|
58
|
+
const nestedParts = buildContextParts(fieldValue, nestedFields, indent + ' ');
|
|
59
|
+
parts.push(...nestedParts);
|
|
60
|
+
} else {
|
|
61
|
+
// Leaf value - format with prompt
|
|
62
|
+
let valueStr;
|
|
63
|
+
if (typeof fieldValue === 'object') {
|
|
64
|
+
valueStr = JSON.stringify(fieldValue, null, 2);
|
|
65
|
+
} else {
|
|
66
|
+
valueStr = String(fieldValue);
|
|
67
|
+
}
|
|
68
|
+
if (customPrompt) {
|
|
69
|
+
parts.push(`${indent}${customPrompt}: ${valueStr}`);
|
|
70
|
+
} else {
|
|
71
|
+
parts.push(`${indent}${fieldName}: ${valueStr}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return parts;
|
|
77
|
+
}
|
|
78
|
+
export const payloadForgeAi = (pluginOptions)=>(config)=>{
|
|
79
|
+
if (!config.collections) {
|
|
80
|
+
config.collections = [];
|
|
81
|
+
}
|
|
82
|
+
if (pluginOptions.disabled) {
|
|
83
|
+
return config;
|
|
84
|
+
}
|
|
85
|
+
const openAi = createOpenAI({
|
|
86
|
+
apiKey: pluginOptions.apiKey
|
|
87
|
+
});
|
|
88
|
+
const model = openAi('gpt-5');
|
|
89
|
+
const systemMessages = [
|
|
90
|
+
{
|
|
91
|
+
content: `
|
|
92
|
+
You are a helpful assistant that can help with the creation of content in payloadCMS fields. You must return only the content and not any other questions or comments!
|
|
93
|
+
The content must be in the same language as the prompt.
|
|
94
|
+
The content must be in the same format as the field type.
|
|
95
|
+
The content must be in the same length as the field type.
|
|
96
|
+
The content must be in the same style as the field type.
|
|
97
|
+
The content must be in the same tone as the field type.
|
|
98
|
+
The content must be in the same voice as the field type.
|
|
99
|
+
You may vary the content to make it more interesting and engaging, but you must always return the content in the same language as the prompt.
|
|
100
|
+
`,
|
|
101
|
+
role: 'system'
|
|
102
|
+
}
|
|
103
|
+
];
|
|
104
|
+
const endpoints = config.endpoints ?? [];
|
|
105
|
+
if (pluginOptions.collections) {
|
|
106
|
+
for(const collectionSlug in pluginOptions.collections){
|
|
107
|
+
const entry = pluginOptions.collections[collectionSlug];
|
|
108
|
+
if (!entry?.fields?.length) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const collection = config.collections.find((c)=>c.slug === collectionSlug);
|
|
112
|
+
if (!collection) {
|
|
113
|
+
throw new InvalidConfiguration(`payload-forge-ai: Collection "${collectionSlug}" is not defined in config.collections.`);
|
|
114
|
+
}
|
|
115
|
+
const fieldNames = entry.fields.map((f)=>f.name);
|
|
116
|
+
validateCollectionFields(collectionSlug, collection, fieldNames);
|
|
117
|
+
entry.fields.forEach((field)=>{
|
|
118
|
+
// Validate context fields
|
|
119
|
+
if (field.context && field.context.length > 0) {
|
|
120
|
+
const contextFieldNames = field.context.map((ctx)=>typeof ctx === 'string' ? ctx : ctx.field);
|
|
121
|
+
validateCollectionFields(collectionSlug, collection, contextFieldNames);
|
|
122
|
+
}
|
|
123
|
+
endpoints.push({
|
|
124
|
+
handler: async (req)=>{
|
|
125
|
+
const payload = req.payload;
|
|
126
|
+
const flattened = flattenTopLevelFields(collection.fields);
|
|
127
|
+
const fieldData = flattened.find((f)=>f.name === field.name);
|
|
128
|
+
if (!fieldData) {
|
|
129
|
+
throw new InvalidConfiguration(`payload-forge-ai: Collection "${collectionSlug}" does not have field "${field.name}".`);
|
|
130
|
+
}
|
|
131
|
+
// Parse request body for context
|
|
132
|
+
let contextData = {};
|
|
133
|
+
let documentId;
|
|
134
|
+
let currentValue = undefined;
|
|
135
|
+
try {
|
|
136
|
+
if (req.json) {
|
|
137
|
+
const body = await req.json();
|
|
138
|
+
contextData = body.context || {};
|
|
139
|
+
documentId = body.documentId;
|
|
140
|
+
currentValue = body.currentValue;
|
|
141
|
+
}
|
|
142
|
+
} catch (e) {
|
|
143
|
+
// If no body or invalid JSON, continue without context
|
|
144
|
+
}
|
|
145
|
+
// Build context prompt from configured context fields
|
|
146
|
+
let contextPrompt = '';
|
|
147
|
+
if (field.context && field.context.length > 0) {
|
|
148
|
+
const contextParts = [];
|
|
149
|
+
for (const ctx of field.context){
|
|
150
|
+
const fieldName = typeof ctx === 'string' ? ctx : ctx.field;
|
|
151
|
+
const customPrompt = typeof ctx === 'string' ? undefined : ctx.prompt;
|
|
152
|
+
const selectedFields = typeof ctx === 'string' ? undefined : ctx.fields;
|
|
153
|
+
let fieldValue = contextData[fieldName];
|
|
154
|
+
if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
|
|
155
|
+
const contextField = flattened.find((f)=>f.name === fieldName);
|
|
156
|
+
const fieldType = contextField?.type || 'unknown';
|
|
157
|
+
// Handle relationship fields - fetch the related document if we only have an ID
|
|
158
|
+
if (contextField?.type === 'relationship' && typeof fieldValue === 'string') {
|
|
159
|
+
try {
|
|
160
|
+
const relationTo = contextField.relationTo;
|
|
161
|
+
if (relationTo) {
|
|
162
|
+
const relatedDoc = await payload.findByID({
|
|
163
|
+
collection: relationTo,
|
|
164
|
+
id: fieldValue
|
|
165
|
+
});
|
|
166
|
+
fieldValue = relatedDoc;
|
|
167
|
+
}
|
|
168
|
+
} catch (e) {
|
|
169
|
+
console.error(`Failed to fetch related document for ${fieldName}:`, e);
|
|
170
|
+
// Continue with the ID if fetch fails
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Handle nested fields with recursive prompts
|
|
174
|
+
if (selectedFields && selectedFields.length > 0 && typeof fieldValue === 'object') {
|
|
175
|
+
// Add the outer prompt as a header if it exists
|
|
176
|
+
if (customPrompt) {
|
|
177
|
+
contextParts.push(`${customPrompt}:`);
|
|
178
|
+
}
|
|
179
|
+
console.log(`Building nested parts for field: ${fieldName}`);
|
|
180
|
+
console.log(`Field value keys:`, Object.keys(fieldValue));
|
|
181
|
+
console.log(`Selected fields config:`, JSON.stringify(selectedFields, null, 2));
|
|
182
|
+
const nestedParts = buildContextParts(fieldValue, selectedFields);
|
|
183
|
+
console.log(`Nested parts result:`, nestedParts);
|
|
184
|
+
contextParts.push(...nestedParts);
|
|
185
|
+
} else {
|
|
186
|
+
// Simple field without nesting
|
|
187
|
+
let valueStr;
|
|
188
|
+
if (typeof fieldValue === 'object') {
|
|
189
|
+
valueStr = JSON.stringify(fieldValue, null, 2);
|
|
190
|
+
} else {
|
|
191
|
+
valueStr = String(fieldValue);
|
|
192
|
+
}
|
|
193
|
+
if (customPrompt) {
|
|
194
|
+
contextParts.push(`${customPrompt}: ${valueStr}`);
|
|
195
|
+
} else {
|
|
196
|
+
contextParts.push(`${fieldName} (${fieldType}): ${valueStr}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (contextParts.length > 0) {
|
|
202
|
+
contextPrompt = '\n\nContext from other fields:\n' + contextParts.join('\n');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Add current value info if it exists
|
|
206
|
+
let currentValuePrompt = '';
|
|
207
|
+
const shouldGenerateDifferent = field.generateDifferent ?? true // Default to true
|
|
208
|
+
;
|
|
209
|
+
if (currentValue !== undefined && currentValue !== null && currentValue !== '') {
|
|
210
|
+
if (shouldGenerateDifferent) {
|
|
211
|
+
currentValuePrompt = `\n\nCurrent value: ${currentValue}\nIMPORTANT: Generate something DIFFERENT from the current value above. Do not repeat it.`;
|
|
212
|
+
} else {
|
|
213
|
+
currentValuePrompt = `\n\nCurrent value: ${currentValue}`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const messages = [
|
|
217
|
+
...systemMessages,
|
|
218
|
+
{
|
|
219
|
+
content: `
|
|
220
|
+
Collection: ${collectionSlug}
|
|
221
|
+
Field name: ${fieldData.name}
|
|
222
|
+
Field type: ${fieldData.type}
|
|
223
|
+
Custom instructions: ${field.prompt}${contextPrompt}${currentValuePrompt}
|
|
224
|
+
`,
|
|
225
|
+
role: 'system'
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
console.log(messages[messages.length - 1].content);
|
|
229
|
+
const response = await generateText({
|
|
230
|
+
messages,
|
|
231
|
+
model
|
|
232
|
+
});
|
|
233
|
+
return Response.json(response.text);
|
|
234
|
+
},
|
|
235
|
+
method: 'post',
|
|
236
|
+
path: `/forge-ai/${collectionSlug}/${field.name}`
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const collectionSlugs = Object.keys(pluginOptions?.collections ?? {});
|
|
242
|
+
return {
|
|
243
|
+
...config,
|
|
244
|
+
collections: [
|
|
245
|
+
...(config.collections ?? []).filter((c)=>!collectionSlugs.includes(c.slug)),
|
|
246
|
+
...collectionSlugs.map((collectionSlug)=>{
|
|
247
|
+
const collection = config.collections?.find((c)=>c.slug === collectionSlug);
|
|
248
|
+
if (!collection) {
|
|
249
|
+
throw new InvalidConfiguration(`payload-forge-ai: Collection "${collectionSlug}" is not defined in config.collections.`);
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
...collection,
|
|
253
|
+
fields: [
|
|
254
|
+
...collection.fields.filter((f)=>{
|
|
255
|
+
if (!hasName(f)) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return pluginOptions.collections?.[collectionSlug]?.fields?.find((field)=>field.name === f.name) === undefined;
|
|
259
|
+
}).map((f)=>{
|
|
260
|
+
return f;
|
|
261
|
+
}),
|
|
262
|
+
...pluginOptions.collections?.[collectionSlug]?.fields?.map((field)=>{
|
|
263
|
+
const fieldData = collection.fields.filter(hasName).find((f)=>f.name === field.name);
|
|
264
|
+
if (!fieldData) {
|
|
265
|
+
throw new InvalidConfiguration(`payload-forge-ai: Collection "${collectionSlug}" does not have field "${field.name}".`);
|
|
266
|
+
}
|
|
267
|
+
return setAdminFieldComponent(fieldData, 'payload-forge-ai/ForgeAIField#ForgeAIField');
|
|
268
|
+
}) ?? []
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
})
|
|
272
|
+
],
|
|
273
|
+
endpoints: [
|
|
274
|
+
...config.endpoints || [],
|
|
275
|
+
...endpoints
|
|
276
|
+
]
|
|
277
|
+
};
|
|
278
|
+
// messages.push({
|
|
279
|
+
// content:
|
|
280
|
+
// 'You are a helpful assistant that can help with the creation of content for a website.',
|
|
281
|
+
// role: 'system',
|
|
282
|
+
// })
|
|
283
|
+
// if (!config.admin) {
|
|
284
|
+
// config.admin = {}
|
|
285
|
+
// }
|
|
286
|
+
// if (!config.admin.components) {
|
|
287
|
+
// config.admin.components = {}
|
|
288
|
+
// }
|
|
289
|
+
// if (!config.admin.components.beforeDashboard) {
|
|
290
|
+
// config.admin.components.beforeDashboard = []
|
|
291
|
+
// }
|
|
292
|
+
// config.admin.components.beforeDashboard.push(`payload-forge-ai/client#BeforeDashboardClient`)
|
|
293
|
+
// config.admin.components.beforeDashboard.push(`payload-forge-ai/rsc#BeforeDashboardServer`)
|
|
294
|
+
};
|
|
295
|
+
const hasName = (f)=>typeof f.name === 'string';
|
|
296
|
+
export function setAdminFieldComponent(field, fieldComponent) {
|
|
297
|
+
return {
|
|
298
|
+
...field,
|
|
299
|
+
admin: {
|
|
300
|
+
...field.admin ?? {},
|
|
301
|
+
components: {
|
|
302
|
+
...field.admin?.components ?? {},
|
|
303
|
+
Field: fieldComponent
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Config, Field, PayloadRequest } from 'payload'\n\nimport { createOpenAI } from '@ai-sdk/openai'\nimport { generateText, type ModelMessage } from 'ai'\nimport { flattenTopLevelFields, InvalidConfiguration } from 'payload'\n\nexport type PayloadForgeAiContextField =\n | string\n | {\n field: string\n prompt?: string\n /**\n * For relationship fields, specify which fields to extract from the related document.\n * For other fields, specify which nested properties to extract.\n * If not specified, the entire value will be used.\n * Can be nested for complex object structures.\n */\n fields?: PayloadForgeAiContextField[]\n }\n\nexport type PayloadForgeAiFieldConfig = {\n context?: PayloadForgeAiContextField[]\n /**\n * If true, tells the AI to generate something different from the current value.\n * Defaults to true.\n */\n generateDifferent?: boolean\n name: string\n prompt: string\n}\n\nexport type PayloadForgeAiCollectionConfig = {\n fields: PayloadForgeAiFieldConfig[]\n}\n\nexport type PayloadForgeAiConfig = {\n apiKey: string\n collections?: Partial<Record<CollectionSlug, PayloadForgeAiCollectionConfig>>\n disabled?: boolean\n}\n\nfunction getValidFieldNames(collection: CollectionConfig): Set<string> {\n const flattened = flattenTopLevelFields(collection.fields)\n return new Set(flattened.map((f) => f.name).filter((name): name is string => name != null))\n}\n\nfunction validateCollectionFields(\n collectionSlug: string,\n collection: CollectionConfig,\n allowedFields: string[],\n): void {\n const validNames = getValidFieldNames(collection)\n const invalid = allowedFields.filter((name) => !validNames.has(name))\n if (invalid.length > 0) {\n throw new InvalidConfiguration(\n `payload-forge-ai: Collection \"${collectionSlug}\" does not have these fields: ${invalid.join(', ')}. ` +\n `Valid top-level fields: ${[...validNames].sort().join(', ')}.`,\n )\n }\n}\n\n/**\n * Recursively extract fields from an object based on field configuration\n */\nfunction extractFields(data: any, fieldConfigs: PayloadForgeAiContextField[]): Record<string, any> {\n if (!data || typeof data !== 'object') {\n return data\n }\n\n const result: Record<string, any> = {}\n\n for (const config of fieldConfigs) {\n const fieldName = typeof config === 'string' ? config : config.field\n const nestedFields = typeof config === 'string' ? undefined : config.fields\n\n if (data[fieldName] !== undefined) {\n if (nestedFields && nestedFields.length > 0) {\n // Recursively extract nested fields\n result[fieldName] = extractFields(data[fieldName], nestedFields)\n } else {\n // Just include the field value\n result[fieldName] = data[fieldName]\n }\n }\n }\n\n return result\n}\n\n/**\n * Recursively build context parts with prompts for nested fields\n */\nfunction buildContextParts(\n data: any,\n fieldConfigs: PayloadForgeAiContextField[],\n indent: string = ' ',\n): string[] {\n if (!data || typeof data !== 'object') {\n return []\n }\n\n const parts: string[] = []\n\n for (const config of fieldConfigs) {\n const fieldName = typeof config === 'string' ? config : config.field\n const customPrompt = typeof config === 'string' ? undefined : config.prompt\n const nestedFields = typeof config === 'string' ? undefined : config.fields\n const fieldValue = data[fieldName]\n\n if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {\n if (nestedFields && nestedFields.length > 0 && typeof fieldValue === 'object') {\n // Handle nested fields with their own prompts\n // Add the current field's prompt as a header if it exists\n if (customPrompt) {\n parts.push(`${indent}${customPrompt}:`)\n }\n const nestedParts = buildContextParts(fieldValue, nestedFields, indent + ' ')\n parts.push(...nestedParts)\n } else {\n // Leaf value - format with prompt\n let valueStr: string\n if (typeof fieldValue === 'object') {\n valueStr = JSON.stringify(fieldValue, null, 2)\n } else {\n valueStr = String(fieldValue)\n }\n\n if (customPrompt) {\n parts.push(`${indent}${customPrompt}: ${valueStr}`)\n } else {\n parts.push(`${indent}${fieldName}: ${valueStr}`)\n }\n }\n }\n }\n\n return parts\n}\n\nexport const payloadForgeAi =\n (pluginOptions: PayloadForgeAiConfig) =>\n (config: Config): Config => {\n if (!config.collections) {\n config.collections = []\n }\n\n if (pluginOptions.disabled) {\n return config\n }\n\n const openAi = createOpenAI({\n apiKey: pluginOptions.apiKey,\n })\n\n const model = openAi('gpt-5')\n\n const systemMessages: ModelMessage[] = [\n {\n content: `\n You are a helpful assistant that can help with the creation of content in payloadCMS fields. You must return only the content and not any other questions or comments!\n The content must be in the same language as the prompt.\n The content must be in the same format as the field type.\n The content must be in the same length as the field type.\n The content must be in the same style as the field type.\n The content must be in the same tone as the field type.\n The content must be in the same voice as the field type.\n You may vary the content to make it more interesting and engaging, but you must always return the content in the same language as the prompt.\n `,\n role: 'system',\n },\n ]\n const endpoints = config.endpoints ?? []\n\n if (pluginOptions.collections) {\n for (const collectionSlug in pluginOptions.collections) {\n const entry = pluginOptions.collections[collectionSlug]\n if (!entry?.fields?.length) {\n continue\n }\n\n const collection = config.collections.find(\n (c: CollectionConfig) => c.slug === collectionSlug,\n )\n if (!collection) {\n throw new InvalidConfiguration(\n `payload-forge-ai: Collection \"${collectionSlug}\" is not defined in config.collections.`,\n )\n }\n\n const fieldNames = entry.fields.map((f) => f.name)\n\n validateCollectionFields(collectionSlug, collection, fieldNames)\n\n entry.fields.forEach((field) => {\n // Validate context fields\n if (field.context && field.context.length > 0) {\n const contextFieldNames = field.context.map((ctx) =>\n typeof ctx === 'string' ? ctx : ctx.field,\n )\n validateCollectionFields(collectionSlug, collection, contextFieldNames)\n }\n\n endpoints.push({\n handler: async (req: PayloadRequest) => {\n const payload = req.payload\n const flattened = flattenTopLevelFields(collection.fields)\n\n const fieldData = flattened.find((f) => f.name === field.name)\n if (!fieldData) {\n throw new InvalidConfiguration(\n `payload-forge-ai: Collection \"${collectionSlug}\" does not have field \"${field.name}\".`,\n )\n }\n\n // Parse request body for context\n let contextData: Record<string, any> = {}\n let documentId: string | undefined\n let currentValue: any = undefined\n\n try {\n if (req.json) {\n const body = await req.json()\n contextData = body.context || {}\n documentId = body.documentId\n currentValue = body.currentValue\n }\n } catch (e) {\n // If no body or invalid JSON, continue without context\n }\n\n // Build context prompt from configured context fields\n let contextPrompt = ''\n if (field.context && field.context.length > 0) {\n const contextParts: string[] = []\n\n for (const ctx of field.context) {\n const fieldName = typeof ctx === 'string' ? ctx : ctx.field\n const customPrompt = typeof ctx === 'string' ? undefined : ctx.prompt\n const selectedFields = typeof ctx === 'string' ? undefined : ctx.fields\n let fieldValue = contextData[fieldName]\n\n if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {\n const contextField = flattened.find((f) => f.name === fieldName)\n const fieldType = contextField?.type || 'unknown'\n\n // Handle relationship fields - fetch the related document if we only have an ID\n if (contextField?.type === 'relationship' && typeof fieldValue === 'string') {\n try {\n const relationTo = (contextField as any).relationTo\n if (relationTo) {\n const relatedDoc = await payload.findByID({\n collection: relationTo,\n id: fieldValue,\n })\n fieldValue = relatedDoc\n }\n } catch (e) {\n console.error(`Failed to fetch related document for ${fieldName}:`, e)\n // Continue with the ID if fetch fails\n }\n }\n\n // Handle nested fields with recursive prompts\n if (\n selectedFields &&\n selectedFields.length > 0 &&\n typeof fieldValue === 'object'\n ) {\n // Add the outer prompt as a header if it exists\n if (customPrompt) {\n contextParts.push(`${customPrompt}:`)\n }\n console.log(`Building nested parts for field: ${fieldName}`)\n console.log(`Field value keys:`, Object.keys(fieldValue))\n console.log(\n `Selected fields config:`,\n JSON.stringify(selectedFields, null, 2),\n )\n const nestedParts = buildContextParts(fieldValue, selectedFields)\n console.log(`Nested parts result:`, nestedParts)\n contextParts.push(...nestedParts)\n } else {\n // Simple field without nesting\n let valueStr: string\n if (typeof fieldValue === 'object') {\n valueStr = JSON.stringify(fieldValue, null, 2)\n } else {\n valueStr = String(fieldValue)\n }\n\n if (customPrompt) {\n contextParts.push(`${customPrompt}: ${valueStr}`)\n } else {\n contextParts.push(`${fieldName} (${fieldType}): ${valueStr}`)\n }\n }\n }\n }\n\n if (contextParts.length > 0) {\n contextPrompt = '\\n\\nContext from other fields:\\n' + contextParts.join('\\n')\n }\n }\n\n // Add current value info if it exists\n let currentValuePrompt = ''\n const shouldGenerateDifferent = field.generateDifferent ?? true // Default to true\n if (currentValue !== undefined && currentValue !== null && currentValue !== '') {\n if (shouldGenerateDifferent) {\n currentValuePrompt = `\\n\\nCurrent value: ${currentValue}\\nIMPORTANT: Generate something DIFFERENT from the current value above. Do not repeat it.`\n } else {\n currentValuePrompt = `\\n\\nCurrent value: ${currentValue}`\n }\n }\n\n const messages: ModelMessage[] = [\n ...systemMessages,\n {\n content: `\nCollection: ${collectionSlug}\nField name: ${fieldData.name}\nField type: ${fieldData.type}\nCustom instructions: ${field.prompt}${contextPrompt}${currentValuePrompt}\n `,\n role: 'system',\n },\n ]\n\n console.log(messages[messages.length - 1].content)\n\n const response = await generateText({\n messages,\n model,\n })\n\n return Response.json(response.text)\n },\n method: 'post',\n path: `/forge-ai/${collectionSlug}/${field.name}`,\n })\n })\n }\n }\n\n const collectionSlugs = Object.keys(pluginOptions?.collections ?? {})\n\n return {\n ...config,\n collections: [\n ...(config.collections ?? []).filter(\n (c: CollectionConfig) => !collectionSlugs.includes(c.slug),\n ),\n ...collectionSlugs.map((collectionSlug) => {\n const collection = config.collections?.find(\n (c: CollectionConfig) => c.slug === collectionSlug,\n )\n\n if (!collection) {\n throw new InvalidConfiguration(\n `payload-forge-ai: Collection \"${collectionSlug}\" is not defined in config.collections.`,\n )\n }\n return {\n ...collection,\n fields: [\n ...collection.fields\n .filter((f: Field) => {\n if (!hasName(f)) {\n return true\n }\n return (\n pluginOptions.collections?.[collectionSlug]?.fields?.find(\n (field) => field.name === f.name,\n ) === undefined\n )\n })\n .map((f: Field) => {\n return f\n }),\n ...(pluginOptions.collections?.[collectionSlug]?.fields?.map((field) => {\n const fieldData = collection.fields\n .filter(hasName)\n .find((f: NamedField) => f.name === field.name) as Field | undefined\n\n if (!fieldData) {\n throw new InvalidConfiguration(\n `payload-forge-ai: Collection \"${collectionSlug}\" does not have field \"${field.name}\".`,\n )\n }\n\n return setAdminFieldComponent(\n fieldData,\n 'payload-forge-ai/ForgeAIField#ForgeAIField',\n )\n }) ?? []),\n ],\n }\n }),\n ],\n endpoints: [...(config.endpoints || []), ...endpoints],\n }\n\n // messages.push({\n // content:\n // 'You are a helpful assistant that can help with the creation of content for a website.',\n // role: 'system',\n // })\n\n // if (!config.admin) {\n // config.admin = {}\n // }\n\n // if (!config.admin.components) {\n // config.admin.components = {}\n // }\n\n // if (!config.admin.components.beforeDashboard) {\n // config.admin.components.beforeDashboard = []\n // }\n\n // config.admin.components.beforeDashboard.push(`payload-forge-ai/client#BeforeDashboardClient`)\n // config.admin.components.beforeDashboard.push(`payload-forge-ai/rsc#BeforeDashboardServer`)\n }\n\ntype NamedField = { name: string } & Field\n\nconst hasName = (f: Field): f is NamedField => typeof (f as any).name === 'string'\n\nexport function setAdminFieldComponent<T extends Field>(field: T, fieldComponent: string): T {\n return {\n ...field,\n admin: {\n ...(field.admin ?? {}),\n components: {\n ...(field.admin?.components ?? {}),\n Field: fieldComponent,\n },\n } as T['admin'], // <- critical: prevents Admin.position widening to string\n }\n}\n"],"names":["createOpenAI","generateText","flattenTopLevelFields","InvalidConfiguration","getValidFieldNames","collection","flattened","fields","Set","map","f","name","filter","validateCollectionFields","collectionSlug","allowedFields","validNames","invalid","has","length","join","sort","extractFields","data","fieldConfigs","result","config","fieldName","field","nestedFields","undefined","buildContextParts","indent","parts","customPrompt","prompt","fieldValue","push","nestedParts","valueStr","JSON","stringify","String","payloadForgeAi","pluginOptions","collections","disabled","openAi","apiKey","model","systemMessages","content","role","endpoints","entry","find","c","slug","fieldNames","forEach","context","contextFieldNames","ctx","handler","req","payload","fieldData","contextData","documentId","currentValue","json","body","e","contextPrompt","contextParts","selectedFields","contextField","fieldType","type","relationTo","relatedDoc","findByID","id","console","error","log","Object","keys","currentValuePrompt","shouldGenerateDifferent","generateDifferent","messages","response","Response","text","method","path","collectionSlugs","includes","hasName","setAdminFieldComponent","fieldComponent","admin","components","Field"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "payload-forgeai",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A plugin for Payload CMS to generate content with AI",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
|
21
|
+
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
22
|
+
"build:types": "tsc --outDir dist --rootDir ./src",
|
|
23
|
+
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
24
|
+
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
25
|
+
"dev": "next dev dev --turbo",
|
|
26
|
+
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
27
|
+
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
28
|
+
"dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
|
|
29
|
+
"generate:importmap": "pnpm dev:generate-importmap",
|
|
30
|
+
"generate:types": "pnpm dev:generate-types",
|
|
31
|
+
"lint": "eslint",
|
|
32
|
+
"lint:fix": "eslint ./src --fix",
|
|
33
|
+
"test": "pnpm test:int && pnpm test:e2e",
|
|
34
|
+
"test:e2e": "playwright test",
|
|
35
|
+
"test:int": "vitest"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
39
|
+
"@payloadcms/db-mongodb": "3.69.0",
|
|
40
|
+
"@payloadcms/db-postgres": "3.69.0",
|
|
41
|
+
"@payloadcms/db-sqlite": "3.69.0",
|
|
42
|
+
"@payloadcms/eslint-config": "3.9.0",
|
|
43
|
+
"@payloadcms/next": "3.69.0",
|
|
44
|
+
"@payloadcms/richtext-lexical": "3.69.0",
|
|
45
|
+
"@payloadcms/ui": "3.69.0",
|
|
46
|
+
"@playwright/test": "1.56.1",
|
|
47
|
+
"@swc-node/register": "1.10.9",
|
|
48
|
+
"@swc/cli": "0.6.0",
|
|
49
|
+
"@types/node": "^22.5.4",
|
|
50
|
+
"@types/react": "19.2.9",
|
|
51
|
+
"@types/react-dom": "19.2.3",
|
|
52
|
+
"copyfiles": "2.4.1",
|
|
53
|
+
"cross-env": "^7.0.3",
|
|
54
|
+
"eslint": "^9.23.0",
|
|
55
|
+
"eslint-config-next": "15.4.11",
|
|
56
|
+
"graphql": "^16.8.1",
|
|
57
|
+
"mongodb-memory-server": "10.1.4",
|
|
58
|
+
"next": "16.1.6",
|
|
59
|
+
"open": "^10.1.0",
|
|
60
|
+
"payload": "3.69.0",
|
|
61
|
+
"prettier": "^3.4.2",
|
|
62
|
+
"qs-esm": "7.0.2",
|
|
63
|
+
"react": "19.2.1",
|
|
64
|
+
"react-dom": "19.2.1",
|
|
65
|
+
"rimraf": "3.0.2",
|
|
66
|
+
"sharp": "0.34.2",
|
|
67
|
+
"sort-package-json": "^2.10.0",
|
|
68
|
+
"typescript": "5.7.3",
|
|
69
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
70
|
+
"vitest": "^3.1.2"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"payload": "^3.69.0"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=20.9.0",
|
|
77
|
+
"pnpm": "^9 || ^10"
|
|
78
|
+
},
|
|
79
|
+
"publishConfig": {
|
|
80
|
+
"exports": {
|
|
81
|
+
".": {
|
|
82
|
+
"import": "./dist/index.js",
|
|
83
|
+
"types": "./dist/index.d.ts",
|
|
84
|
+
"default": "./dist/index.js"
|
|
85
|
+
},
|
|
86
|
+
"./ForgeAIField": {
|
|
87
|
+
"import": "./dist/exports/ForgeAIField.js",
|
|
88
|
+
"types": "./dist/exports/ForgeAIField.ts",
|
|
89
|
+
"default": "./dist/exports/ForgeAIField.js"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"main": "./dist/index.js",
|
|
93
|
+
"types": "./dist/index.d.ts"
|
|
94
|
+
},
|
|
95
|
+
"pnpm": {
|
|
96
|
+
"onlyBuiltDependencies": [
|
|
97
|
+
"sharp",
|
|
98
|
+
"esbuild",
|
|
99
|
+
"unrs-resolver"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
"registry": "https://registry.npmjs.org/",
|
|
103
|
+
"dependencies": {
|
|
104
|
+
"@ai-sdk/openai": "^3.0.25",
|
|
105
|
+
"ai": "^6.0.71"
|
|
106
|
+
}
|
|
107
|
+
}
|