notes-to-strapi-export-article-ai 1.0.10 → 1.0.12
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/.github/workflows/deploy.yml +89 -55
- package/README.md +10 -10
- package/esbuild.config.mjs +1 -1
- package/manifest.json +1 -1
- package/package.json +3 -2
- package/src/constants.ts +67 -0
- package/src/main.ts +61 -0
- package/src/settings.ts +418 -0
- package/src/types/article.ts +8 -0
- package/src/types/image.ts +20 -0
- package/src/types/settings.ts +28 -0
- package/src/utils/image-processor.ts +426 -0
- package/src/utils/openai-generator.ts +139 -0
- package/src/utils/strapi-uploader.ts +127 -0
- package/src/utils/validators.ts +8 -0
- package/versions.json +3 -1
- package/main.ts +0 -691
- package/styles.css +0 -8
- /package/{img.png → images/img.png} +0 -0
- /package/{img_1.png → images/img_1.png} +0 -0
- /package/{img_2.png → images/img_2.png} +0 -0
- /package/{img_3.png → images/img_3.png} +0 -0
- /package/{img_4.png → images/img_4.png} +0 -0
- /package/{img_5.png → images/img_5.png} +0 -0
- /package/{img_6.png → images/img_6.png} +0 -0
- /package/{img_7.png → images/img_7.png} +0 -0
- /package/{img_8.png → images/img_8.png} +0 -0
- /package/{img_9.png → images/img_9.png} +0 -0
package/main.ts
DELETED
|
@@ -1,691 +0,0 @@
|
|
|
1
|
-
import OpenAI from 'openai'
|
|
2
|
-
import {
|
|
3
|
-
App,
|
|
4
|
-
MarkdownView,
|
|
5
|
-
Notice,
|
|
6
|
-
Plugin,
|
|
7
|
-
PluginSettingTab,
|
|
8
|
-
Setting,
|
|
9
|
-
TFile,
|
|
10
|
-
} from 'obsidian'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The settings for the Strapi Exporter plugin
|
|
14
|
-
*/
|
|
15
|
-
interface StrapiExporterSettings {
|
|
16
|
-
strapiUrl: string
|
|
17
|
-
strapiApiToken: string
|
|
18
|
-
openaiApiKey: string
|
|
19
|
-
jsonTemplate: string
|
|
20
|
-
jsonTemplateDescription: string
|
|
21
|
-
strapiArticleCreateUrl: string
|
|
22
|
-
strapiContentAttributeName: string
|
|
23
|
-
additionalPrompt: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The default settings for the plugin
|
|
28
|
-
*/
|
|
29
|
-
const DEFAULT_STRAPI_EXPORTER_SETTINGS: StrapiExporterSettings = {
|
|
30
|
-
strapiUrl: '',
|
|
31
|
-
strapiApiToken: '',
|
|
32
|
-
openaiApiKey: '',
|
|
33
|
-
jsonTemplate: `{
|
|
34
|
-
"data": {
|
|
35
|
-
"title": "string",
|
|
36
|
-
"seo_title": "string",
|
|
37
|
-
"seo_description": "string",
|
|
38
|
-
"slug": "string",
|
|
39
|
-
"excerpt": "string",
|
|
40
|
-
"links": [
|
|
41
|
-
{
|
|
42
|
-
"id": "number",
|
|
43
|
-
"label": "string",
|
|
44
|
-
"url": "string"
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"subtitle": "string",
|
|
48
|
-
"type": "string",
|
|
49
|
-
"rank": "number",
|
|
50
|
-
"tags": [
|
|
51
|
-
{
|
|
52
|
-
"id": "number",
|
|
53
|
-
"name": "string"
|
|
54
|
-
}
|
|
55
|
-
],
|
|
56
|
-
"locale": "string"
|
|
57
|
-
}
|
|
58
|
-
}`,
|
|
59
|
-
jsonTemplateDescription: `{
|
|
60
|
-
"data": {
|
|
61
|
-
"title": "Title of the item, as a short string",
|
|
62
|
-
"seo_title": "SEO optimized title, as a short string",
|
|
63
|
-
"seo_description": "SEO optimized description, as a short string",
|
|
64
|
-
"slug": "URL-friendly string derived from the title",
|
|
65
|
-
"excerpt": "A short preview or snippet from the content",
|
|
66
|
-
"links": "Array of related links with ID, label, and URL",
|
|
67
|
-
"subtitle": "Subtitle or secondary title, as a short string",
|
|
68
|
-
"type": "Category or type of the item, as a short string",
|
|
69
|
-
"rank": "Numerical ranking or order priority, as a number",
|
|
70
|
-
"tags": "Array of associated tags with ID and name",
|
|
71
|
-
"locale": "Locale or language code, as a short string"
|
|
72
|
-
}
|
|
73
|
-
}`,
|
|
74
|
-
strapiArticleCreateUrl: '',
|
|
75
|
-
strapiContentAttributeName: '',
|
|
76
|
-
additionalPrompt: '',
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The main plugin class
|
|
81
|
-
*/
|
|
82
|
-
export default class StrapiExporterPlugin extends Plugin {
|
|
83
|
-
settings: StrapiExporterSettings
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* The main entry point for the plugin
|
|
87
|
-
*/
|
|
88
|
-
async onload() {
|
|
89
|
-
await this.loadSettings()
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Add a ribbon icon to the Markdown view (the little icon on the left side bar)
|
|
93
|
-
*/
|
|
94
|
-
const ribbonIconEl = this.addRibbonIcon(
|
|
95
|
-
'upload',
|
|
96
|
-
'Upload images to Strapi and update links in Markdown content, then generate article content using OpenAI',
|
|
97
|
-
async (evt: MouseEvent) => {
|
|
98
|
-
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView)
|
|
99
|
-
if (!activeView) {
|
|
100
|
-
new Notice('No active Markdown view')
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/** ****************************************************************************
|
|
105
|
-
* Check if all the settings are configured
|
|
106
|
-
* *****************************************************************************
|
|
107
|
-
*/
|
|
108
|
-
if (!this.settings.strapiUrl || !this.settings.strapiApiToken) {
|
|
109
|
-
new Notice(
|
|
110
|
-
'Please configure Strapi URL and API token in the plugin settings'
|
|
111
|
-
)
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!this.settings.openaiApiKey) {
|
|
116
|
-
new Notice('Please configure OpenAI API key in the plugin settings')
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!this.settings.jsonTemplate) {
|
|
121
|
-
new Notice('Please configure JSON template in the plugin settings')
|
|
122
|
-
return
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!this.settings.jsonTemplateDescription) {
|
|
126
|
-
new Notice(
|
|
127
|
-
'Please configure JSON template description in the plugin settings'
|
|
128
|
-
)
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (!this.settings.strapiArticleCreateUrl) {
|
|
133
|
-
new Notice(
|
|
134
|
-
'Please configure Strapi article create URL in the plugin settings'
|
|
135
|
-
)
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!this.settings.strapiContentAttributeName) {
|
|
140
|
-
new Notice(
|
|
141
|
-
'Please configure Strapi content attribute name in the plugin settings'
|
|
142
|
-
)
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** ****************************************************************************
|
|
147
|
-
* Process the Markdown content
|
|
148
|
-
* *****************************************************************************
|
|
149
|
-
*/
|
|
150
|
-
new Notice('All settings are ok, processing Markdown content...')
|
|
151
|
-
const file = activeView.file
|
|
152
|
-
let content = ''
|
|
153
|
-
if (!file) {
|
|
154
|
-
new Notice('No file found in active view...')
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Read the content of the file
|
|
159
|
-
*/
|
|
160
|
-
content = await this.app.vault.read(file)
|
|
161
|
-
|
|
162
|
-
// check if the content has any images to process
|
|
163
|
-
const flag = this.hasUnexportedImages(content)
|
|
164
|
-
/**
|
|
165
|
-
* Initialize the OpenAI API
|
|
166
|
-
*/
|
|
167
|
-
const openai = new OpenAI({
|
|
168
|
-
apiKey: this.settings.openaiApiKey,
|
|
169
|
-
dangerouslyAllowBrowser: true,
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Process the images in the content, upload them to Strapi, and update the links,
|
|
174
|
-
* only if there are images in the content
|
|
175
|
-
* that are not already uploaded to Strapi
|
|
176
|
-
*/
|
|
177
|
-
if (flag) {
|
|
178
|
-
/**
|
|
179
|
-
* Extract the image paths from the content
|
|
180
|
-
*/
|
|
181
|
-
const imagePaths = this.extractImagePaths(content)
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Get the image blobs from the image paths
|
|
185
|
-
*/
|
|
186
|
-
const imageBlobs = await this.getImageBlobs(imagePaths)
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Get the image descriptions using the OpenAI API
|
|
190
|
-
*/
|
|
191
|
-
new Notice('Getting image descriptions...')
|
|
192
|
-
const imageDescriptions = await Promise.all(
|
|
193
|
-
imageBlobs.map(async imageBlob => {
|
|
194
|
-
const description = await this.getImageDescription(
|
|
195
|
-
imageBlob.blob,
|
|
196
|
-
openai
|
|
197
|
-
)
|
|
198
|
-
return {
|
|
199
|
-
blob: imageBlob.blob,
|
|
200
|
-
name: imageBlob.name,
|
|
201
|
-
path: imageBlob.path,
|
|
202
|
-
description,
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Upload the images to Strapi
|
|
209
|
-
*/
|
|
210
|
-
new Notice('Uploading images to Strapi...')
|
|
211
|
-
const uploadedImages =
|
|
212
|
-
await this.uploadImagesToStrapi(imageDescriptions)
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Replace the image paths in the content with the uploaded image URLs
|
|
216
|
-
*/
|
|
217
|
-
new Notice('Replacing image paths...')
|
|
218
|
-
content = this.replaceImagePaths(content, uploadedImages)
|
|
219
|
-
await this.app.vault.modify(file, content)
|
|
220
|
-
new Notice('Images uploaded and links updated successfully!')
|
|
221
|
-
} else {
|
|
222
|
-
new Notice(
|
|
223
|
-
'No local images found in the content... Skip the image processing...'
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Generate article content using OpenAI
|
|
229
|
-
*/
|
|
230
|
-
new Notice('Generating article content...')
|
|
231
|
-
/**
|
|
232
|
-
* Parse the JSON template and description
|
|
233
|
-
*/
|
|
234
|
-
const jsonTemplate = JSON.parse(this.settings.jsonTemplate)
|
|
235
|
-
const jsonTemplateDescription = JSON.parse(
|
|
236
|
-
this.settings.jsonTemplateDescription
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* If the content is not present, get it from the active view
|
|
241
|
-
*/
|
|
242
|
-
content = await this.app.vault.read(file)
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Prompt for generating the article content
|
|
246
|
-
*/
|
|
247
|
-
const articlePrompt = `You are an SEO expert. Generate an article based on the following template and field descriptions:
|
|
248
|
-
|
|
249
|
-
Template:
|
|
250
|
-
${JSON.stringify(jsonTemplate, null, 2)}
|
|
251
|
-
|
|
252
|
-
Field Descriptions:
|
|
253
|
-
${JSON.stringify(jsonTemplateDescription, null, 2)}
|
|
254
|
-
|
|
255
|
-
The main content of the article should be based on the following text and all the keywords around the domain of the text:
|
|
256
|
-
----- CONTENT -----
|
|
257
|
-
${content.substring(0, 500)}
|
|
258
|
-
----- END CONTENT -----
|
|
259
|
-
|
|
260
|
-
Please provide the generated article content as a JSON object following the given template structure.
|
|
261
|
-
|
|
262
|
-
${this.settings.additionalPrompt ? `Additional Prompt: ${this.settings.additionalPrompt}` : ''}`
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Generate the article content using OpenAI
|
|
266
|
-
*/
|
|
267
|
-
const completion = await openai.chat.completions.create({
|
|
268
|
-
model: 'gpt-3.5-turbo-0125',
|
|
269
|
-
messages: [
|
|
270
|
-
{
|
|
271
|
-
role: 'user',
|
|
272
|
-
content: articlePrompt,
|
|
273
|
-
},
|
|
274
|
-
],
|
|
275
|
-
max_tokens: 2000,
|
|
276
|
-
n: 1,
|
|
277
|
-
stop: null,
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Parse the generated article content
|
|
282
|
-
*/
|
|
283
|
-
let articleContent = JSON.parse(
|
|
284
|
-
completion.choices[0].message.content ?? '{}'
|
|
285
|
-
)
|
|
286
|
-
/**
|
|
287
|
-
* Add the content to the article content
|
|
288
|
-
*/
|
|
289
|
-
articleContent = {
|
|
290
|
-
data: {
|
|
291
|
-
...articleContent.data,
|
|
292
|
-
[this.settings.strapiContentAttributeName]: content,
|
|
293
|
-
},
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
new Notice('Article content generated successfully!')
|
|
297
|
-
try {
|
|
298
|
-
const response = await fetch(this.settings.strapiArticleCreateUrl, {
|
|
299
|
-
method: 'POST',
|
|
300
|
-
headers: {
|
|
301
|
-
'Content-Type': 'application/json',
|
|
302
|
-
Authorization: `Bearer ${this.settings.strapiApiToken}`,
|
|
303
|
-
},
|
|
304
|
-
body: JSON.stringify(articleContent),
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
if (response.ok) {
|
|
308
|
-
new Notice('Article created successfully in Strapi!')
|
|
309
|
-
} else {
|
|
310
|
-
new Notice('Failed to create article in Strapi.')
|
|
311
|
-
}
|
|
312
|
-
} catch (error) {
|
|
313
|
-
new Notice('Error creating article in Strapi.')
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
new Notice(
|
|
317
|
-
'Check your API content now, the article is created & uploaded ! 🎉'
|
|
318
|
-
)
|
|
319
|
-
}
|
|
320
|
-
)
|
|
321
|
-
ribbonIconEl.addClass('strapi-exporter-ribbon-class')
|
|
322
|
-
|
|
323
|
-
this.addSettingTab(new StrapiExporterSettingTab(this.app, this))
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
onunload() {}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Load the settings for the plugin
|
|
330
|
-
*/
|
|
331
|
-
async loadSettings() {
|
|
332
|
-
this.settings = Object.assign(
|
|
333
|
-
{},
|
|
334
|
-
DEFAULT_STRAPI_EXPORTER_SETTINGS,
|
|
335
|
-
await this.loadData()
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Save the settings for the plugin
|
|
341
|
-
*/
|
|
342
|
-
async saveSettings() {
|
|
343
|
-
await this.saveData(this.settings)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Extract the image paths from the content
|
|
348
|
-
* @param content
|
|
349
|
-
*/
|
|
350
|
-
extractImagePaths(content: string): string[] {
|
|
351
|
-
/**
|
|
352
|
-
* Extract the image paths from the content
|
|
353
|
-
*/
|
|
354
|
-
const imageRegex = /!\[\[([^\[\]]*\.(png|jpe?g|gif|bmp|webp))\]\]/gi
|
|
355
|
-
const imagePaths: string[] = []
|
|
356
|
-
let match
|
|
357
|
-
|
|
358
|
-
while ((match = imageRegex.exec(content)) !== null) {
|
|
359
|
-
imagePaths.push(match[1])
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return imagePaths
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Check if the content has any unexported images
|
|
367
|
-
* @param content
|
|
368
|
-
*/
|
|
369
|
-
hasUnexportedImages(content: string): boolean {
|
|
370
|
-
const imageRegex = /!\[\[([^\[\]]*\.(png|jpe?g|gif|bmp|webp))\]\]/gi
|
|
371
|
-
return imageRegex.test(content)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Get the image blobs from the image paths
|
|
376
|
-
* @param imagePaths
|
|
377
|
-
*/
|
|
378
|
-
async getImageBlobs(
|
|
379
|
-
imagePaths: string[]
|
|
380
|
-
): Promise<{ path: string; blob: Blob; name: string }[]> {
|
|
381
|
-
// get all the files in the vault
|
|
382
|
-
const files = this.app.vault.getAllLoadedFiles()
|
|
383
|
-
// get the image files name from the vault
|
|
384
|
-
const fileNames = files.map(file => file.name)
|
|
385
|
-
// filter the image files, and get all the images files paths
|
|
386
|
-
const imageFiles = imagePaths.filter(path => fileNames.includes(path))
|
|
387
|
-
// get the image blobs, find it, and return the blob
|
|
388
|
-
return await Promise.all(
|
|
389
|
-
imageFiles.map(async path => {
|
|
390
|
-
const file = files.find(file => file.name === path)
|
|
391
|
-
if (file instanceof TFile) {
|
|
392
|
-
const blob = await this.app.vault.readBinary(file)
|
|
393
|
-
return {
|
|
394
|
-
name: path,
|
|
395
|
-
blob: new Blob([blob], { type: 'image/png' }),
|
|
396
|
-
path: file.path,
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return {
|
|
400
|
-
name: '',
|
|
401
|
-
blob: new Blob(),
|
|
402
|
-
path: '',
|
|
403
|
-
}
|
|
404
|
-
})
|
|
405
|
-
)
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Upload the images to Strapi
|
|
410
|
-
* @param imageBlobs
|
|
411
|
-
*/
|
|
412
|
-
async uploadImagesToStrapi(
|
|
413
|
-
imageBlobs: {
|
|
414
|
-
path: string
|
|
415
|
-
blob: Blob
|
|
416
|
-
name: string
|
|
417
|
-
description: {
|
|
418
|
-
name: string
|
|
419
|
-
alternativeText: string
|
|
420
|
-
caption: string
|
|
421
|
-
}
|
|
422
|
-
}[]
|
|
423
|
-
): Promise<{ [key: string]: { url: string; data: any } }> {
|
|
424
|
-
// upload the images to Strapi
|
|
425
|
-
const uploadedImages: {
|
|
426
|
-
[key: string]: { url: string; data: any }
|
|
427
|
-
} = {}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Upload the images to Strapi
|
|
431
|
-
*/
|
|
432
|
-
for (const imageBlob of imageBlobs) {
|
|
433
|
-
const formData = new FormData()
|
|
434
|
-
/**
|
|
435
|
-
* Append the image blob and the image description to the form data
|
|
436
|
-
*/
|
|
437
|
-
formData.append('files', imageBlob.blob, imageBlob.name)
|
|
438
|
-
formData.append(
|
|
439
|
-
'fileInfo',
|
|
440
|
-
JSON.stringify({
|
|
441
|
-
name: imageBlob.description.name,
|
|
442
|
-
alternativeText: imageBlob.description.alternativeText,
|
|
443
|
-
caption: imageBlob.description.caption,
|
|
444
|
-
})
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
// upload the image to Strapi
|
|
448
|
-
try {
|
|
449
|
-
const response = await fetch(`${this.settings.strapiUrl}/api/upload`, {
|
|
450
|
-
method: 'POST',
|
|
451
|
-
headers: {
|
|
452
|
-
Authorization: `Bearer ${this.settings.strapiApiToken}`,
|
|
453
|
-
},
|
|
454
|
-
body: formData,
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* If the response is ok, add the uploaded image to the uploaded images object
|
|
459
|
-
*/
|
|
460
|
-
if (response.ok) {
|
|
461
|
-
const data = await response.json()
|
|
462
|
-
uploadedImages[imageBlob.name] = {
|
|
463
|
-
url: data[0].url,
|
|
464
|
-
data: data[0],
|
|
465
|
-
}
|
|
466
|
-
} else {
|
|
467
|
-
new Notice(`Failed to upload image: ${imageBlob.name}`)
|
|
468
|
-
}
|
|
469
|
-
} catch (error) {
|
|
470
|
-
new Notice(`Error uploading image: ${imageBlob.name}`)
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return uploadedImages
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Replace the image paths in the content with the uploaded image URLs
|
|
479
|
-
* @param content
|
|
480
|
-
* @param uploadedImages
|
|
481
|
-
*/
|
|
482
|
-
replaceImagePaths(
|
|
483
|
-
content: string,
|
|
484
|
-
uploadedImages: { [key: string]: { url: string; data: any } }
|
|
485
|
-
): string {
|
|
486
|
-
/**
|
|
487
|
-
* Replace the image paths in the content with the uploaded image URLs
|
|
488
|
-
*/
|
|
489
|
-
for (const [localPath, imageData] of Object.entries(uploadedImages)) {
|
|
490
|
-
const markdownImageRegex = new RegExp(`!\\[\\[${localPath}\\]\\]`, 'g')
|
|
491
|
-
content = content.replace(
|
|
492
|
-
markdownImageRegex,
|
|
493
|
-
``
|
|
494
|
-
)
|
|
495
|
-
}
|
|
496
|
-
return content
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Get the description of the image using OpenAI
|
|
501
|
-
* @param imageBlob
|
|
502
|
-
* @param openai
|
|
503
|
-
*/
|
|
504
|
-
async getImageDescription(imageBlob: Blob, openai: OpenAI) {
|
|
505
|
-
// get the image description using the OpenAI API ( using gpt 4 vision preview model )
|
|
506
|
-
const response = await openai.chat.completions.create({
|
|
507
|
-
model: 'gpt-4-vision-preview',
|
|
508
|
-
messages: [
|
|
509
|
-
{
|
|
510
|
-
role: 'user',
|
|
511
|
-
// @ts-ignore
|
|
512
|
-
content: [
|
|
513
|
-
{
|
|
514
|
-
type: 'text',
|
|
515
|
-
text: `What's in this image? make it simple, i just want the context and an idea(think about alt text)`,
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
type: 'image_url',
|
|
519
|
-
// encode imageBlob as base64
|
|
520
|
-
image_url: `data:image/png;base64,${btoa(
|
|
521
|
-
new Uint8Array(await imageBlob.arrayBuffer()).reduce(
|
|
522
|
-
(data, byte) => data + String.fromCharCode(byte),
|
|
523
|
-
''
|
|
524
|
-
)
|
|
525
|
-
)}`,
|
|
526
|
-
},
|
|
527
|
-
],
|
|
528
|
-
},
|
|
529
|
-
],
|
|
530
|
-
})
|
|
531
|
-
|
|
532
|
-
new Notice(response.choices[0].message.content ?? 'no response content...')
|
|
533
|
-
new Notice(
|
|
534
|
-
`prompt_tokens: ${response.usage?.prompt_tokens} // completion_tokens: ${response.usage?.completion_tokens} // total_tokens: ${response.usage?.total_tokens}`
|
|
535
|
-
)
|
|
536
|
-
|
|
537
|
-
// gpt-3.5-turbo-0125
|
|
538
|
-
// alt text, caption, and title for the image, based on the description of the image
|
|
539
|
-
const completion = await openai.chat.completions.create({
|
|
540
|
-
model: 'gpt-3.5-turbo-0125',
|
|
541
|
-
messages: [
|
|
542
|
-
{
|
|
543
|
-
role: 'user',
|
|
544
|
-
content: `You are an SEO expert and you are writing alt text, caption, and title for this image. The description of the image is: ${response.choices[0].message.content}.
|
|
545
|
-
Give me a title (name) for this image, an SEO-friendly alternative text, and a caption for this image.
|
|
546
|
-
Generate this information and respond with a JSON object using the following fields: name, alternativeText, caption.
|
|
547
|
-
Use this JSON template: {"name": "string", "alternativeText": "string", "caption": "string"}.`,
|
|
548
|
-
},
|
|
549
|
-
],
|
|
550
|
-
max_tokens: 750,
|
|
551
|
-
n: 1,
|
|
552
|
-
stop: null,
|
|
553
|
-
})
|
|
554
|
-
|
|
555
|
-
new Notice(
|
|
556
|
-
completion.choices[0].message.content ?? 'no response content...'
|
|
557
|
-
)
|
|
558
|
-
new Notice(
|
|
559
|
-
`prompt_tokens: ${completion.usage?.prompt_tokens} // completion_tokens: ${completion.usage?.completion_tokens} // total_tokens: ${completion.usage?.total_tokens}`
|
|
560
|
-
)
|
|
561
|
-
|
|
562
|
-
return JSON.parse(completion.choices[0].message.content?.trim() || '{}')
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* The settings tab for the Strapi Exporter plugin
|
|
568
|
-
*/
|
|
569
|
-
class StrapiExporterSettingTab extends PluginSettingTab {
|
|
570
|
-
plugin: StrapiExporterPlugin
|
|
571
|
-
|
|
572
|
-
constructor(app: App, plugin: StrapiExporterPlugin) {
|
|
573
|
-
super(app, plugin)
|
|
574
|
-
this.plugin = plugin
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
display(): void {
|
|
578
|
-
const { containerEl } = this
|
|
579
|
-
containerEl.empty()
|
|
580
|
-
|
|
581
|
-
/** ****************************************************************************
|
|
582
|
-
* Add the settings for the plugin
|
|
583
|
-
* *****************************************************************************
|
|
584
|
-
*/
|
|
585
|
-
new Setting(containerEl)
|
|
586
|
-
.setName('Strapi URL')
|
|
587
|
-
.setDesc('Enter your Strapi instance URL')
|
|
588
|
-
.addText(text =>
|
|
589
|
-
text
|
|
590
|
-
.setPlaceholder('https://your-strapi-url')
|
|
591
|
-
.setValue(this.plugin.settings.strapiUrl)
|
|
592
|
-
.onChange(async value => {
|
|
593
|
-
this.plugin.settings.strapiUrl = value
|
|
594
|
-
await this.plugin.saveSettings()
|
|
595
|
-
})
|
|
596
|
-
)
|
|
597
|
-
|
|
598
|
-
new Setting(containerEl)
|
|
599
|
-
.setName('Strapi API Token')
|
|
600
|
-
.setDesc('Enter your Strapi API token')
|
|
601
|
-
.addText(text =>
|
|
602
|
-
text
|
|
603
|
-
.setPlaceholder('Enter your token')
|
|
604
|
-
.setValue(this.plugin.settings.strapiApiToken)
|
|
605
|
-
.onChange(async value => {
|
|
606
|
-
this.plugin.settings.strapiApiToken = value
|
|
607
|
-
await this.plugin.saveSettings()
|
|
608
|
-
})
|
|
609
|
-
)
|
|
610
|
-
|
|
611
|
-
new Setting(containerEl)
|
|
612
|
-
.setName('OpenAI API Key')
|
|
613
|
-
.setDesc('Enter your OpenAI API key for GPT-3')
|
|
614
|
-
.addText(text =>
|
|
615
|
-
text
|
|
616
|
-
.setPlaceholder('Enter your OpenAI API key')
|
|
617
|
-
.setValue(this.plugin.settings.openaiApiKey)
|
|
618
|
-
.onChange(async value => {
|
|
619
|
-
this.plugin.settings.openaiApiKey = value
|
|
620
|
-
await this.plugin.saveSettings()
|
|
621
|
-
})
|
|
622
|
-
)
|
|
623
|
-
|
|
624
|
-
new Setting(containerEl)
|
|
625
|
-
.setName('JSON Template')
|
|
626
|
-
.setDesc('Enter the JSON template for the fields needed')
|
|
627
|
-
.addTextArea(text =>
|
|
628
|
-
text
|
|
629
|
-
.setPlaceholder('Enter your JSON template')
|
|
630
|
-
.setValue(this.plugin.settings.jsonTemplate)
|
|
631
|
-
.onChange(async value => {
|
|
632
|
-
this.plugin.settings.jsonTemplate = value
|
|
633
|
-
await this.plugin.saveSettings()
|
|
634
|
-
})
|
|
635
|
-
)
|
|
636
|
-
|
|
637
|
-
new Setting(containerEl)
|
|
638
|
-
.setName('JSON Template Description')
|
|
639
|
-
.setDesc('Enter the description for each field in the JSON template')
|
|
640
|
-
.addTextArea(text =>
|
|
641
|
-
text
|
|
642
|
-
.setPlaceholder('Enter the field descriptions')
|
|
643
|
-
.setValue(this.plugin.settings.jsonTemplateDescription)
|
|
644
|
-
.onChange(async value => {
|
|
645
|
-
this.plugin.settings.jsonTemplateDescription = value
|
|
646
|
-
await this.plugin.saveSettings()
|
|
647
|
-
})
|
|
648
|
-
)
|
|
649
|
-
|
|
650
|
-
new Setting(containerEl)
|
|
651
|
-
.setName('Strapi Article Create URL')
|
|
652
|
-
.setDesc('Enter the URL to create articles in Strapi')
|
|
653
|
-
.addText(text =>
|
|
654
|
-
text
|
|
655
|
-
.setPlaceholder('https://your-strapi-url/api/articles')
|
|
656
|
-
.setValue(this.plugin.settings.strapiArticleCreateUrl)
|
|
657
|
-
.onChange(async value => {
|
|
658
|
-
this.plugin.settings.strapiArticleCreateUrl = value
|
|
659
|
-
await this.plugin.saveSettings()
|
|
660
|
-
})
|
|
661
|
-
)
|
|
662
|
-
|
|
663
|
-
new Setting(containerEl)
|
|
664
|
-
.setName('Strapi Content Attribute Name')
|
|
665
|
-
.setDesc('Enter the attribute name for the content field in Strapi')
|
|
666
|
-
.addText(text =>
|
|
667
|
-
text
|
|
668
|
-
.setPlaceholder('content')
|
|
669
|
-
.setValue(this.plugin.settings.strapiContentAttributeName)
|
|
670
|
-
.onChange(async value => {
|
|
671
|
-
this.plugin.settings.strapiContentAttributeName = value
|
|
672
|
-
await this.plugin.saveSettings()
|
|
673
|
-
})
|
|
674
|
-
)
|
|
675
|
-
|
|
676
|
-
new Setting(containerEl)
|
|
677
|
-
.setName('Additional Prompt')
|
|
678
|
-
.setDesc(
|
|
679
|
-
'Enter an optional additional prompt to customize the article content generation'
|
|
680
|
-
)
|
|
681
|
-
.addTextArea(text =>
|
|
682
|
-
text
|
|
683
|
-
.setPlaceholder('Enter your additional prompt here...')
|
|
684
|
-
.setValue(this.plugin.settings.additionalPrompt)
|
|
685
|
-
.onChange(async value => {
|
|
686
|
-
this.plugin.settings.additionalPrompt = value
|
|
687
|
-
await this.plugin.saveSettings()
|
|
688
|
-
})
|
|
689
|
-
)
|
|
690
|
-
}
|
|
691
|
-
}
|
package/styles.css
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|