notes-to-strapi-export-article-ai 1.0.11 → 1.0.13
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
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { App, MarkdownView, Notice, TFile, TFolder } from 'obsidian'
|
|
2
|
+
import { OpenAI } from 'openai'
|
|
3
|
+
import { StrapiExporterSettings } from '../types/settings'
|
|
4
|
+
import {
|
|
5
|
+
uploadGalleryImagesToStrapi,
|
|
6
|
+
uploadImagesToStrapi,
|
|
7
|
+
} from './strapi-uploader'
|
|
8
|
+
import { generateArticleContent, getImageDescription } from './openai-generator'
|
|
9
|
+
import { ImageBlob } from '../types/image'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Process the markdown content
|
|
13
|
+
* @param app
|
|
14
|
+
* @param settings
|
|
15
|
+
* @param useAdditionalCallAPI
|
|
16
|
+
*/
|
|
17
|
+
export async function processMarkdownContent(
|
|
18
|
+
app: App,
|
|
19
|
+
settings: StrapiExporterSettings,
|
|
20
|
+
useAdditionalCallAPI = false
|
|
21
|
+
) {
|
|
22
|
+
const activeView = app.workspace.getActiveViewOfType(MarkdownView)
|
|
23
|
+
if (!activeView) {
|
|
24
|
+
new Notice('No active Markdown view')
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** ****************************************************************************
|
|
29
|
+
* Check if all the settings are configured
|
|
30
|
+
* *****************************************************************************
|
|
31
|
+
*/
|
|
32
|
+
if (!settings.strapiUrl || !settings.strapiApiToken) {
|
|
33
|
+
new Notice(
|
|
34
|
+
'Please configure Strapi URL and API token in the plugin settings'
|
|
35
|
+
)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!settings.openaiApiKey) {
|
|
40
|
+
new Notice('Please configure OpenAI API key in the plugin settings')
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (useAdditionalCallAPI) {
|
|
45
|
+
if (!settings.additionalJsonTemplate) {
|
|
46
|
+
new Notice(
|
|
47
|
+
'Please configure the additional call api JSON template in the plugin settings'
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!settings.additionalJsonTemplateDescription) {
|
|
53
|
+
new Notice(
|
|
54
|
+
'Please configure the additional call api JSON template description in the plugin settings'
|
|
55
|
+
)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!settings.additionalUrl) {
|
|
60
|
+
new Notice(
|
|
61
|
+
'Please configure the additional call api URL in the plugin settings'
|
|
62
|
+
)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!settings.additionalContentAttributeName) {
|
|
67
|
+
new Notice(
|
|
68
|
+
'Please configure the additional call api content attribute name in the plugin settings'
|
|
69
|
+
)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
if (!settings.jsonTemplate) {
|
|
74
|
+
new Notice('Please configure JSON template in the plugin settings')
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!settings.jsonTemplateDescription) {
|
|
79
|
+
new Notice(
|
|
80
|
+
'Please configure JSON template description in the plugin settings'
|
|
81
|
+
)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!settings.strapiArticleCreateUrl) {
|
|
86
|
+
new Notice(
|
|
87
|
+
'Please configure Strapi article create URL in the plugin settings'
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!settings.strapiContentAttributeName) {
|
|
93
|
+
new Notice(
|
|
94
|
+
'Please configure Strapi content attribute name in the plugin settings'
|
|
95
|
+
)
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
new Notice('All settings are ok, processing Markdown content...')
|
|
101
|
+
|
|
102
|
+
// Initialize OpenAI API
|
|
103
|
+
const openai = new OpenAI({
|
|
104
|
+
apiKey: settings.openaiApiKey,
|
|
105
|
+
dangerouslyAllowBrowser: true,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
/** ****************************************************************************
|
|
109
|
+
* Process the markdown content
|
|
110
|
+
* *****************************************************************************
|
|
111
|
+
*/
|
|
112
|
+
const file = activeView.file
|
|
113
|
+
let content = ''
|
|
114
|
+
if (!file) {
|
|
115
|
+
new Notice('No file found in active view...')
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** ****************************************************************************
|
|
120
|
+
* Check if the content has any images to process
|
|
121
|
+
* *****************************************************************************
|
|
122
|
+
*/
|
|
123
|
+
let imageBlob: ImageBlob | null = null
|
|
124
|
+
let galleryUploadedImageIds: number[] = []
|
|
125
|
+
const articleFolderPath = file.parent?.path
|
|
126
|
+
const imageFolderPath = `${articleFolderPath}/image`
|
|
127
|
+
const galleryFolderPath = `${articleFolderPath}/gallery`
|
|
128
|
+
const imageMetadataFile = `${articleFolderPath}/image/metadata.json`
|
|
129
|
+
const galleryMetadataFile = `${articleFolderPath}/gallery/metadata.json`
|
|
130
|
+
|
|
131
|
+
// Check if metadata files exist
|
|
132
|
+
if (await app.vault.adapter.exists(imageMetadataFile)) {
|
|
133
|
+
const imageMetadata = JSON.parse(
|
|
134
|
+
await app.vault.adapter.read(imageMetadataFile)
|
|
135
|
+
)
|
|
136
|
+
if (Object.keys(imageMetadata).length > 0) {
|
|
137
|
+
imageBlob = {
|
|
138
|
+
path: imageMetadata[Object.keys(imageMetadata)[0]].data.url,
|
|
139
|
+
blob: new Blob(),
|
|
140
|
+
name: Object.keys(imageMetadata)[0],
|
|
141
|
+
id: imageMetadata[Object.keys(imageMetadata)[0]].id,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (await app.vault.adapter.exists(galleryMetadataFile)) {
|
|
147
|
+
const galleryMetadata = JSON.parse(
|
|
148
|
+
await app.vault.adapter.read(galleryMetadataFile)
|
|
149
|
+
)
|
|
150
|
+
if (Object.keys(galleryMetadata).length > 0) {
|
|
151
|
+
galleryUploadedImageIds = Object.values(galleryMetadata).map(
|
|
152
|
+
(item: any) => item.data.id
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If metadata doesn't exist, get image blobs and upload to Strapi
|
|
158
|
+
if (!imageBlob) {
|
|
159
|
+
imageBlob = await getImageBlob(app, imageFolderPath)
|
|
160
|
+
if (imageBlob) {
|
|
161
|
+
const imageDescription = await getImageDescription(imageBlob.blob, openai)
|
|
162
|
+
const uploadedImage: any = await uploadImagesToStrapi(
|
|
163
|
+
[{ ...imageBlob, description: imageDescription }],
|
|
164
|
+
settings,
|
|
165
|
+
app,
|
|
166
|
+
imageFolderPath
|
|
167
|
+
)
|
|
168
|
+
imageBlob.path = uploadedImage[imageBlob.name].url
|
|
169
|
+
imageBlob.id = uploadedImage[imageBlob.name].id
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (galleryUploadedImageIds.length === 0) {
|
|
174
|
+
const galleryImageBlobs = await getGalleryImageBlobs(app, galleryFolderPath)
|
|
175
|
+
galleryUploadedImageIds = await uploadGalleryImagesToStrapi(
|
|
176
|
+
galleryImageBlobs,
|
|
177
|
+
settings,
|
|
178
|
+
app,
|
|
179
|
+
galleryFolderPath
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
content = await app.vault.read(file)
|
|
184
|
+
|
|
185
|
+
const flag = hasUnexportedImages(content)
|
|
186
|
+
|
|
187
|
+
/** ****************************************************************************
|
|
188
|
+
* Process the images
|
|
189
|
+
* *****************************************************************************
|
|
190
|
+
*/
|
|
191
|
+
if (flag) {
|
|
192
|
+
const imagePaths = extractImagePaths(content)
|
|
193
|
+
const imageBlobs = await getImageBlobs(app, imagePaths)
|
|
194
|
+
|
|
195
|
+
new Notice('Getting image descriptions...')
|
|
196
|
+
const imageDescriptions = await Promise.all(
|
|
197
|
+
imageBlobs.map(async imageBlob => {
|
|
198
|
+
const description = await getImageDescription(imageBlob.blob, openai)
|
|
199
|
+
return {
|
|
200
|
+
blob: imageBlob.blob,
|
|
201
|
+
name: imageBlob.name,
|
|
202
|
+
path: imageBlob.path,
|
|
203
|
+
description,
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
new Notice('Uploading images to Strapi...')
|
|
209
|
+
const uploadedImages = await uploadImagesToStrapi(
|
|
210
|
+
imageDescriptions,
|
|
211
|
+
settings
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
new Notice('Replacing image paths...')
|
|
215
|
+
content = replaceImagePaths(content, uploadedImages)
|
|
216
|
+
await app.vault.modify(file, content)
|
|
217
|
+
new Notice('Images uploaded and links updated successfully!')
|
|
218
|
+
} else {
|
|
219
|
+
new Notice(
|
|
220
|
+
'No local images found in the content... Skip the image processing...'
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** ****************************************************************************
|
|
225
|
+
* Generate article content
|
|
226
|
+
* *****************************************************************************
|
|
227
|
+
*/
|
|
228
|
+
new Notice('Generating article content...')
|
|
229
|
+
const articleContent = await generateArticleContent(
|
|
230
|
+
content,
|
|
231
|
+
openai,
|
|
232
|
+
settings,
|
|
233
|
+
useAdditionalCallAPI
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
/** ****************************************************************************
|
|
237
|
+
* Add the content, image, and gallery to the article content based on the settings
|
|
238
|
+
* *****************************************************************************
|
|
239
|
+
*/
|
|
240
|
+
const imageFullPathProperty = useAdditionalCallAPI
|
|
241
|
+
? settings.additionalImageFullPathProperty
|
|
242
|
+
: settings.mainImageFullPathProperty
|
|
243
|
+
const galleryFullPathProperty = useAdditionalCallAPI
|
|
244
|
+
? settings.additionalGalleryFullPathProperty
|
|
245
|
+
: settings.mainGalleryFullPathProperty
|
|
246
|
+
|
|
247
|
+
articleContent.data = {
|
|
248
|
+
...articleContent.data,
|
|
249
|
+
...(imageBlob &&
|
|
250
|
+
imageFullPathProperty && { [imageFullPathProperty]: imageBlob.id }),
|
|
251
|
+
...(galleryUploadedImageIds.length > 0 &&
|
|
252
|
+
galleryFullPathProperty && {
|
|
253
|
+
[galleryFullPathProperty]: galleryUploadedImageIds,
|
|
254
|
+
}),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
new Notice('Article content generated successfully!')
|
|
258
|
+
try {
|
|
259
|
+
const response = await fetch(
|
|
260
|
+
useAdditionalCallAPI
|
|
261
|
+
? settings.additionalUrl
|
|
262
|
+
: settings.strapiArticleCreateUrl,
|
|
263
|
+
{
|
|
264
|
+
method: 'POST',
|
|
265
|
+
headers: {
|
|
266
|
+
'Content-Type': 'application/json',
|
|
267
|
+
Authorization: `Bearer ${settings.strapiApiToken}`,
|
|
268
|
+
},
|
|
269
|
+
body: JSON.stringify(articleContent),
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if (response.ok) {
|
|
274
|
+
new Notice(
|
|
275
|
+
'Check your API content now, the article is created & uploaded! 🎉'
|
|
276
|
+
)
|
|
277
|
+
} else {
|
|
278
|
+
new Notice('Failed to create article in Strapi.')
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
new Notice('Error creating article in Strapi.')
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Extract image paths from the markdown content
|
|
287
|
+
* @param content
|
|
288
|
+
*/
|
|
289
|
+
export function extractImagePaths(content: string): string[] {
|
|
290
|
+
const imageRegex = /!\[\[([^\[\]]*\.(png|jpe?g|gif|bmp|webp))\]\]/gi
|
|
291
|
+
const imagePaths: string[] = []
|
|
292
|
+
let match
|
|
293
|
+
|
|
294
|
+
while ((match = imageRegex.exec(content)) !== null) {
|
|
295
|
+
imagePaths.push(match[1])
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return imagePaths
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Check if the markdown content has unexported images
|
|
303
|
+
* @param content
|
|
304
|
+
*/
|
|
305
|
+
export function hasUnexportedImages(content: string): boolean {
|
|
306
|
+
const imageRegex = /!\[\[([^\[\]]*\.(png|jpe?g|gif|bmp|webp))\]\]/gi
|
|
307
|
+
return imageRegex.test(content)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Get the image blobs from the image paths
|
|
312
|
+
* @param app
|
|
313
|
+
* @param imagePaths
|
|
314
|
+
*/
|
|
315
|
+
export async function getImageBlobs(
|
|
316
|
+
app: App,
|
|
317
|
+
imagePaths: string[]
|
|
318
|
+
): Promise<{ path: string; blob: Blob; name: string }[]> {
|
|
319
|
+
const files = app.vault.getAllLoadedFiles()
|
|
320
|
+
const fileNames = files.map(file => file.name)
|
|
321
|
+
const imageFiles = imagePaths.filter(path => fileNames.includes(path))
|
|
322
|
+
return await Promise.all(
|
|
323
|
+
imageFiles.map(async path => {
|
|
324
|
+
const file = files.find(file => file.name === path)
|
|
325
|
+
if (file instanceof TFile) {
|
|
326
|
+
const blob = await app.vault.readBinary(file)
|
|
327
|
+
return {
|
|
328
|
+
name: path,
|
|
329
|
+
blob: new Blob([blob], { type: 'image/png' }),
|
|
330
|
+
path: file.path,
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
name: '',
|
|
335
|
+
blob: new Blob(),
|
|
336
|
+
path: '',
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Get the image blob from the image path
|
|
344
|
+
* @param app
|
|
345
|
+
* @param imageFolderPath
|
|
346
|
+
*/
|
|
347
|
+
export async function getImageBlob(
|
|
348
|
+
app: App,
|
|
349
|
+
imageFolderPath: string
|
|
350
|
+
): Promise<{ path: string; blob: Blob; name: string } | null> {
|
|
351
|
+
const folder = app.vault.getAbstractFileByPath(imageFolderPath)
|
|
352
|
+
if (folder instanceof TFolder) {
|
|
353
|
+
const files = folder.children.filter(
|
|
354
|
+
file =>
|
|
355
|
+
file instanceof TFile &&
|
|
356
|
+
file.extension.match(/^(jpg|jpeg|png|gif|bmp|webp)$/i)
|
|
357
|
+
)
|
|
358
|
+
if (files.length > 0) {
|
|
359
|
+
const file = files[0] as TFile
|
|
360
|
+
const blob = await app.vault.readBinary(file)
|
|
361
|
+
return {
|
|
362
|
+
name: file.name,
|
|
363
|
+
blob: new Blob([blob], { type: 'image/png' }),
|
|
364
|
+
path: file.path,
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
new Notice(
|
|
369
|
+
'Image folder not found. Please create an "image" folder next to your article file.'
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
return null
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get the gallery image blobs from the folder path
|
|
377
|
+
* @param app
|
|
378
|
+
* @param galleryFolderPath
|
|
379
|
+
*/
|
|
380
|
+
export async function getGalleryImageBlobs(
|
|
381
|
+
app: App,
|
|
382
|
+
galleryFolderPath: string
|
|
383
|
+
): Promise<{ path: string; blob: Blob; name: string }[]> {
|
|
384
|
+
const folder = app.vault.getAbstractFileByPath(galleryFolderPath)
|
|
385
|
+
if (folder instanceof TFolder) {
|
|
386
|
+
const files = folder.children.filter(
|
|
387
|
+
file =>
|
|
388
|
+
file instanceof TFile &&
|
|
389
|
+
file.extension.match(/^(jpg|jpeg|png|gif|bmp|webp)$/i)
|
|
390
|
+
)
|
|
391
|
+
return Promise.all(
|
|
392
|
+
files.map(async file => {
|
|
393
|
+
const blob = await app.vault.readBinary(file as TFile)
|
|
394
|
+
return {
|
|
395
|
+
name: file.name,
|
|
396
|
+
blob: new Blob([blob], { type: 'image/png' }),
|
|
397
|
+
path: file.path,
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
)
|
|
401
|
+
} else {
|
|
402
|
+
new Notice(
|
|
403
|
+
'Gallery folder not found. Please create a "gallery" folder next to your article file.'
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
return []
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Replace the image paths in the content with the uploaded images
|
|
411
|
+
* @param content
|
|
412
|
+
* @param uploadedImages
|
|
413
|
+
*/
|
|
414
|
+
export function replaceImagePaths(
|
|
415
|
+
content: string,
|
|
416
|
+
uploadedImages: { [key: string]: { url: string; data: any } }
|
|
417
|
+
): string {
|
|
418
|
+
for (const [localPath, imageData] of Object.entries(uploadedImages)) {
|
|
419
|
+
const markdownImageRegex = new RegExp(`!\\[\\[${localPath}\\]\\]`, 'g')
|
|
420
|
+
content = content.replace(
|
|
421
|
+
markdownImageRegex,
|
|
422
|
+
``
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
return content
|
|
426
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { OpenAI } from 'openai'
|
|
2
|
+
import { StrapiExporterSettings } from '../types/settings'
|
|
3
|
+
import { ArticleContent } from '../types/article'
|
|
4
|
+
import { Notice } from 'obsidian'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate article content using OpenAI
|
|
8
|
+
* @param content
|
|
9
|
+
* @param openai
|
|
10
|
+
* @param settings
|
|
11
|
+
* @param useAdditionalCallAPI
|
|
12
|
+
*/
|
|
13
|
+
export async function generateArticleContent(
|
|
14
|
+
content: string,
|
|
15
|
+
openai: OpenAI,
|
|
16
|
+
settings: StrapiExporterSettings,
|
|
17
|
+
useAdditionalCallAPI = false
|
|
18
|
+
): Promise<ArticleContent> {
|
|
19
|
+
let jsonTemplate: any
|
|
20
|
+
let jsonTemplateDescription: any
|
|
21
|
+
let contentAttributeName: string
|
|
22
|
+
|
|
23
|
+
if (useAdditionalCallAPI) {
|
|
24
|
+
jsonTemplate = JSON.parse(settings.additionalJsonTemplate)
|
|
25
|
+
jsonTemplateDescription = JSON.parse(
|
|
26
|
+
settings.additionalJsonTemplateDescription
|
|
27
|
+
)
|
|
28
|
+
contentAttributeName = settings.additionalContentAttributeName
|
|
29
|
+
} else {
|
|
30
|
+
jsonTemplate = JSON.parse(settings.jsonTemplate)
|
|
31
|
+
jsonTemplateDescription = JSON.parse(settings.jsonTemplateDescription)
|
|
32
|
+
contentAttributeName = settings.strapiContentAttributeName
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const articlePrompt = `You are an SEO expert. Generate an article based on the following template and field descriptions:
|
|
36
|
+
|
|
37
|
+
Template:
|
|
38
|
+
${JSON.stringify(jsonTemplate, null, 2)}
|
|
39
|
+
|
|
40
|
+
Field Descriptions:
|
|
41
|
+
${JSON.stringify(jsonTemplateDescription, null, 2)}
|
|
42
|
+
|
|
43
|
+
The main content of the article should be based on the following text and all the keywords around the domain of the text:
|
|
44
|
+
----- CONTENT -----
|
|
45
|
+
${content.substring(0, 500)}
|
|
46
|
+
----- END CONTENT -----
|
|
47
|
+
|
|
48
|
+
Please provide the generated article content as a JSON object following the given template structure.
|
|
49
|
+
|
|
50
|
+
${settings.additionalPrompt ? `Additional Prompt: ${settings.additionalPrompt}` : ''}`
|
|
51
|
+
|
|
52
|
+
const completion = await openai.chat.completions.create({
|
|
53
|
+
model: 'gpt-3.5-turbo-0125',
|
|
54
|
+
messages: [
|
|
55
|
+
{
|
|
56
|
+
role: 'user',
|
|
57
|
+
content: articlePrompt,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
max_tokens: 2000,
|
|
61
|
+
n: 1,
|
|
62
|
+
stop: null,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
let articleContent = JSON.parse(completion.choices[0].message.content ?? '{}')
|
|
66
|
+
articleContent = {
|
|
67
|
+
data: {
|
|
68
|
+
...articleContent.data,
|
|
69
|
+
[contentAttributeName]: content,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return articleContent
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the description of the image using OpenAI
|
|
78
|
+
* @param imageBlob
|
|
79
|
+
* @param openai
|
|
80
|
+
*/
|
|
81
|
+
export const getImageDescription = async (imageBlob: Blob, openai: OpenAI) => {
|
|
82
|
+
// Get the image description using the OpenAI API (using gpt 4 vision preview model)
|
|
83
|
+
// @ts-ignore
|
|
84
|
+
const response = await openai.chat.completions.create({
|
|
85
|
+
model: 'gpt-4-vision-preview',
|
|
86
|
+
messages: [
|
|
87
|
+
{
|
|
88
|
+
role: 'user',
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: 'text',
|
|
92
|
+
text: `What's in this image? make it simple, i just want the context and an idea(think about alt text)`,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'image_url',
|
|
96
|
+
// Encode imageBlob as base64
|
|
97
|
+
// @ts-ignore
|
|
98
|
+
image_url: `data:image/png;base64,${btoa(
|
|
99
|
+
new Uint8Array(await imageBlob.arrayBuffer()).reduce(
|
|
100
|
+
(data, byte) => data + String.fromCharCode(byte),
|
|
101
|
+
''
|
|
102
|
+
)
|
|
103
|
+
)}`,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
new Notice(response.choices[0].message.content ?? 'no response content...')
|
|
111
|
+
new Notice(
|
|
112
|
+
`prompt_tokens: ${response.usage?.prompt_tokens} // completion_tokens: ${response.usage?.completion_tokens} // total_tokens: ${response.usage?.total_tokens}`
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
// gpt-3.5-turbo-0125
|
|
116
|
+
// Generate alt text, caption, and title for the image, based on the description of the image
|
|
117
|
+
const completion = await openai.chat.completions.create({
|
|
118
|
+
model: 'gpt-3.5-turbo-0125',
|
|
119
|
+
messages: [
|
|
120
|
+
{
|
|
121
|
+
role: 'user',
|
|
122
|
+
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}.
|
|
123
|
+
Give me a title (name) for this image, an SEO-friendly alternative text, and a caption for this image.
|
|
124
|
+
Generate this information and respond with a JSON object using the following fields: name, alternativeText, caption.
|
|
125
|
+
Use this JSON template: {"name": "string", "alternativeText": "string", "caption": "string"}.`,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
max_tokens: 750,
|
|
129
|
+
n: 1,
|
|
130
|
+
stop: null,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
new Notice(completion.choices[0].message.content ?? 'no response content...')
|
|
134
|
+
new Notice(
|
|
135
|
+
`prompt_tokens: ${completion.usage?.prompt_tokens} // completion_tokens: ${completion.usage?.completion_tokens} // total_tokens: ${completion.usage?.total_tokens}`
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return JSON.parse(completion.choices[0].message.content?.trim() || '{}')
|
|
139
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Notice } from 'obsidian'
|
|
2
|
+
import { StrapiExporterSettings } from '../types/settings'
|
|
3
|
+
import { ImageBlob, ImageDescription } from '../types/image'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Upload images to Strapi
|
|
7
|
+
* @param imageDescriptions
|
|
8
|
+
* @param settings
|
|
9
|
+
* @param app
|
|
10
|
+
* @param imageFolderPath
|
|
11
|
+
*/
|
|
12
|
+
export async function uploadImagesToStrapi(
|
|
13
|
+
imageDescriptions: ImageDescription[],
|
|
14
|
+
settings: StrapiExporterSettings,
|
|
15
|
+
app: any = null,
|
|
16
|
+
imageFolderPath: string = ''
|
|
17
|
+
): Promise<{ [key: string]: { url: string; data: any } }> {
|
|
18
|
+
const uploadedImages: { [key: string]: { url: string; data: any; id: any } } =
|
|
19
|
+
{}
|
|
20
|
+
|
|
21
|
+
for (const imageDescription of imageDescriptions) {
|
|
22
|
+
const formData = new FormData()
|
|
23
|
+
formData.append('files', imageDescription.blob, imageDescription.name)
|
|
24
|
+
formData.append(
|
|
25
|
+
'fileInfo',
|
|
26
|
+
JSON.stringify({
|
|
27
|
+
name: imageDescription.description.name,
|
|
28
|
+
alternativeText: imageDescription.description.alternativeText,
|
|
29
|
+
caption: imageDescription.description.caption,
|
|
30
|
+
})
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(`${settings.strapiUrl}/api/upload`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: `Bearer ${settings.strapiApiToken}`,
|
|
38
|
+
},
|
|
39
|
+
body: formData,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
if (response.ok) {
|
|
43
|
+
const data = await response.json()
|
|
44
|
+
uploadedImages[imageDescription.name] = {
|
|
45
|
+
url: data[0].url,
|
|
46
|
+
data: data[0],
|
|
47
|
+
id: data[0].id,
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
new Notice(`Failed to upload image: ${imageDescription.name}`)
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
new Notice(`Error uploading image: ${imageDescription.name}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (imageFolderPath && app) {
|
|
58
|
+
// Save metadata to a file only if there are uploaded images
|
|
59
|
+
if (Object.keys(uploadedImages).length > 0) {
|
|
60
|
+
const metadataFile = `${imageFolderPath}/metadata.json`
|
|
61
|
+
await app.vault.adapter.write(
|
|
62
|
+
metadataFile,
|
|
63
|
+
JSON.stringify(uploadedImages)
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return uploadedImages
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Upload gallery images to Strapi
|
|
72
|
+
* @param imageBlobs
|
|
73
|
+
* @param settings
|
|
74
|
+
* @param app
|
|
75
|
+
* @param galleryFolderPath
|
|
76
|
+
*/
|
|
77
|
+
export async function uploadGalleryImagesToStrapi(
|
|
78
|
+
imageBlobs: ImageBlob[],
|
|
79
|
+
settings: StrapiExporterSettings,
|
|
80
|
+
app: any = null,
|
|
81
|
+
galleryFolderPath: string = ''
|
|
82
|
+
): Promise<number[]> {
|
|
83
|
+
const uploadedImageIds: number[] = []
|
|
84
|
+
const uploadedImages: { [key: string]: { url: string; data: any; id: any } } =
|
|
85
|
+
{}
|
|
86
|
+
|
|
87
|
+
for (const imageBlob of imageBlobs) {
|
|
88
|
+
const formData = new FormData()
|
|
89
|
+
formData.append('files', imageBlob.blob, imageBlob.name)
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(`${settings.strapiUrl}/api/upload`, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: {
|
|
95
|
+
Authorization: `Bearer ${settings.strapiApiToken}`,
|
|
96
|
+
},
|
|
97
|
+
body: formData,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
if (response.ok) {
|
|
101
|
+
const data = await response.json()
|
|
102
|
+
uploadedImages[imageBlob.name] = {
|
|
103
|
+
url: data[0].url,
|
|
104
|
+
id: data[0].id,
|
|
105
|
+
data: data[0],
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
new Notice(`Failed to upload gallery image: ${imageBlob.name}`)
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
new Notice(`Error uploading gallery image: ${imageBlob.name}`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (galleryFolderPath && app) {
|
|
116
|
+
// Save metadata to a file only if there are uploaded images
|
|
117
|
+
if (Object.keys(uploadedImages).length > 0) {
|
|
118
|
+
const metadataFile = `${galleryFolderPath}/metadata.json`
|
|
119
|
+
await app.vault.adapter.write(
|
|
120
|
+
metadataFile,
|
|
121
|
+
JSON.stringify(uploadedImages)
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return uploadedImageIds
|
|
127
|
+
}
|