notes-to-strapi-export-article-ai 1.0.119 → 3.0.2
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/.eslintrc +30 -22
- package/README.md +98 -143
- package/images/img.png +0 -0
- package/images/img_1.png +0 -0
- package/images/img_10.png +0 -0
- package/images/img_11.png +0 -0
- package/images/img_12.png +0 -0
- package/images/img_13.png +0 -0
- package/images/img_2.png +0 -0
- package/images/img_3.png +0 -0
- package/images/img_4.png +0 -0
- package/images/img_5.png +0 -0
- package/images/img_6.png +0 -0
- package/images/img_7.png +0 -0
- package/images/img_8.png +0 -0
- package/images/img_9.png +0 -0
- package/manifest.json +2 -2
- package/package.json +29 -26
- package/src/components/APIKeys.ts +219 -0
- package/src/components/Configuration.ts +663 -0
- package/src/components/Dashboard.ts +184 -0
- package/src/components/ImageSelectionModal.ts +58 -0
- package/src/components/Routes.ts +279 -0
- package/src/constants.ts +22 -61
- package/src/main.ts +177 -34
- package/src/services/configuration-generator.ts +172 -0
- package/src/services/field-analyzer.ts +84 -0
- package/src/services/frontmatter.ts +329 -0
- package/src/services/strapi-export.ts +436 -0
- package/src/settings/UnifiedSettingsTab.ts +206 -0
- package/src/types/image.ts +27 -16
- package/src/types/index.ts +3 -0
- package/src/types/route.ts +51 -0
- package/src/types/settings.ts +22 -23
- package/src/utils/analyse-file.ts +94 -0
- package/src/utils/debounce.ts +34 -0
- package/src/utils/image-processor.ts +124 -400
- package/src/utils/preview-modal.ts +265 -0
- package/src/utils/process-file.ts +122 -0
- package/src/utils/strapi-uploader.ts +120 -119
- package/src/settings.ts +0 -404
- package/src/types/article.ts +0 -8
- package/src/utils/openai-generator.ts +0 -139
- package/src/utils/validators.ts +0 -8
- package/version-bump.mjs +0 -14
- package/versions.json +0 -119
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { generateText } from 'ai'
|
|
2
|
+
import { createOpenAI } from '@ai-sdk/openai'
|
|
3
|
+
import { App, TFile } from 'obsidian'
|
|
4
|
+
import { RouteConfig } from '../types'
|
|
5
|
+
import StrapiExporterPlugin from '../main'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interface defining the structure of a field in the schema
|
|
9
|
+
*/
|
|
10
|
+
interface FieldMapping {
|
|
11
|
+
obsidianSource: 'frontmatter' | 'content'
|
|
12
|
+
type: string
|
|
13
|
+
description: string
|
|
14
|
+
required: boolean
|
|
15
|
+
format?: string
|
|
16
|
+
transform?: string
|
|
17
|
+
validation?: {
|
|
18
|
+
type: string
|
|
19
|
+
pattern?: string
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Interface for the complete configuration of field mappings
|
|
25
|
+
*/
|
|
26
|
+
interface GeneratedConfig {
|
|
27
|
+
fieldMappings: Record<string, FieldMapping>
|
|
28
|
+
contentField: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* FrontmatterGenerator class handles the generation of YAML frontmatter
|
|
33
|
+
* based on a dynamic schema configuration from Strapi
|
|
34
|
+
*/
|
|
35
|
+
export class FrontmatterGenerator {
|
|
36
|
+
private model
|
|
37
|
+
private plugin: StrapiExporterPlugin
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initialize the generator with the plugin instance
|
|
41
|
+
* @param plugin - The Strapi Exporter plugin instance
|
|
42
|
+
*/
|
|
43
|
+
constructor(plugin: StrapiExporterPlugin) {
|
|
44
|
+
this.plugin = plugin
|
|
45
|
+
const openai = createOpenAI({
|
|
46
|
+
apiKey: this.plugin.settings.openaiApiKey,
|
|
47
|
+
})
|
|
48
|
+
this.model = openai('gpt-4o-mini')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Updates or creates frontmatter in an existing file
|
|
53
|
+
* @param file - The file to update
|
|
54
|
+
* @param app - The Obsidian app instance
|
|
55
|
+
* @returns Promise<string> Updated content with new frontmatter
|
|
56
|
+
*/
|
|
57
|
+
async updateContentFrontmatter(file: TFile, app: App): Promise<string> {
|
|
58
|
+
const content = await app.vault.read(file)
|
|
59
|
+
const newFrontmatter = await this.generateFrontmatter(file, app)
|
|
60
|
+
const updatedContent = this.replaceFrontmatter(content, newFrontmatter)
|
|
61
|
+
|
|
62
|
+
return updatedContent
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generates structured example data based on field type and format
|
|
67
|
+
* @param field - The field mapping configuration
|
|
68
|
+
* @returns An appropriate example value for the field type
|
|
69
|
+
*/
|
|
70
|
+
private generateExampleValue(field: FieldMapping): any {
|
|
71
|
+
const { type, format } = field
|
|
72
|
+
|
|
73
|
+
// Handle arrays with specific structures
|
|
74
|
+
if (type === 'array') {
|
|
75
|
+
return this.generateArrayExample(field)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle media fields
|
|
79
|
+
if (type === 'media') {
|
|
80
|
+
return 'https://example.com/image.jpg'
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Handle standard types with formats
|
|
84
|
+
switch (type) {
|
|
85
|
+
case 'string':
|
|
86
|
+
if (format === 'url') return 'https://example.com'
|
|
87
|
+
if (format === 'slug') return 'example-slug'
|
|
88
|
+
return 'Example text'
|
|
89
|
+
case 'number':
|
|
90
|
+
return 1
|
|
91
|
+
case 'boolean':
|
|
92
|
+
return true
|
|
93
|
+
default:
|
|
94
|
+
return 'Example value'
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generates example array content based on the field's transform configuration
|
|
100
|
+
* @param field - The field mapping configuration
|
|
101
|
+
* @returns An example array structure
|
|
102
|
+
*/
|
|
103
|
+
private generateArrayExample(field: FieldMapping): any {
|
|
104
|
+
if (!field.transform) {
|
|
105
|
+
return ['example1', 'example2']
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Analyze transform string to determine array structure
|
|
109
|
+
const transform = field.transform
|
|
110
|
+
|
|
111
|
+
// Handle different array structures based on transform content
|
|
112
|
+
if (transform.includes('name:')) {
|
|
113
|
+
return [
|
|
114
|
+
{ name: 'example1', id: 1 },
|
|
115
|
+
{ name: 'example2', id: 2 },
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (transform.includes('label:') && transform.includes('url:')) {
|
|
120
|
+
return [
|
|
121
|
+
{ label: 'Example Link', url: 'https://example.com' },
|
|
122
|
+
{ label: 'Another Link', url: 'https://example.com/page' },
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return ['example1', 'example2']
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Builds a detailed prompt for GPT based on the schema configuration
|
|
131
|
+
* @param content - The content to analyze
|
|
132
|
+
* @param config - The generated configuration
|
|
133
|
+
* @returns A structured prompt string
|
|
134
|
+
*/
|
|
135
|
+
private buildPrompt(content: string, config: GeneratedConfig): string {
|
|
136
|
+
const exampleData = {}
|
|
137
|
+
for (const [field, mapping] of Object.entries(config.fieldMappings)) {
|
|
138
|
+
exampleData[field] = this.generateExampleValue(mapping)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const exampleYaml = this.convertToYaml(exampleData)
|
|
142
|
+
const requiredFields = Object.entries(config.fieldMappings)
|
|
143
|
+
.filter(([_, mapping]) => mapping.required)
|
|
144
|
+
.map(([field]) => field)
|
|
145
|
+
.join(', ')
|
|
146
|
+
|
|
147
|
+
const prompt = `Generate YAML frontmatter that follows this exact schema and format:
|
|
148
|
+
|
|
149
|
+
Schema Definition:
|
|
150
|
+
${JSON.stringify(config.fieldMappings, null, 2)}
|
|
151
|
+
|
|
152
|
+
Expected Format Example:
|
|
153
|
+
---
|
|
154
|
+
${exampleYaml}---
|
|
155
|
+
|
|
156
|
+
Requirements:
|
|
157
|
+
1. Maintain exact field names as shown in the schema
|
|
158
|
+
2. Follow YAML formatting and indentation precisely
|
|
159
|
+
3. Generate appropriate content for each field type
|
|
160
|
+
4. Preserve array structures as shown in the example
|
|
161
|
+
5. Required fields: ${requiredFields}
|
|
162
|
+
6. Use quotes for string values
|
|
163
|
+
7. Maintain proper data types (strings, numbers, arrays)
|
|
164
|
+
|
|
165
|
+
Content for Analysis:
|
|
166
|
+
${content.substring(0, 2000)}
|
|
167
|
+
|
|
168
|
+
DO NOT include the content IN the generated frontmatter. Just use the content to generate the frontmatter as context.
|
|
169
|
+
the content field is ${config.contentField}. Please delete the content field from the frontmatter.
|
|
170
|
+
|
|
171
|
+
Return complete YAML frontmatter with opening and closing "---" markers.`
|
|
172
|
+
|
|
173
|
+
return prompt
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Converts a JavaScript object to properly formatted YAML
|
|
178
|
+
* @param obj - The object to convert
|
|
179
|
+
* @param indent - Current indentation level
|
|
180
|
+
* @returns Formatted YAML string
|
|
181
|
+
*/
|
|
182
|
+
private convertToYaml(obj: any, indent: number = 0): string {
|
|
183
|
+
const spaces = ' '.repeat(indent)
|
|
184
|
+
let yaml = ''
|
|
185
|
+
|
|
186
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
187
|
+
if (Array.isArray(value)) {
|
|
188
|
+
yaml += `${spaces}${key}:\n`
|
|
189
|
+
value.forEach(item => {
|
|
190
|
+
if (typeof item === 'object') {
|
|
191
|
+
yaml += `${spaces} -\n`
|
|
192
|
+
Object.entries(item).forEach(([k, v]) => {
|
|
193
|
+
yaml += `${spaces} ${k}: "${v}"\n`
|
|
194
|
+
})
|
|
195
|
+
} else {
|
|
196
|
+
yaml += `${spaces} - "${item}"\n`
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
200
|
+
yaml += `${spaces}${key}:\n`
|
|
201
|
+
yaml += this.convertToYaml(value, indent + 2)
|
|
202
|
+
} else {
|
|
203
|
+
const formattedValue = typeof value === 'string' ? `"${value}"` : value
|
|
204
|
+
yaml += `${spaces}${key}: ${formattedValue}\n`
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return yaml
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Replaces or adds frontmatter in content
|
|
213
|
+
* @param content - Original content
|
|
214
|
+
* @param newFrontmatter - New frontmatter to add
|
|
215
|
+
* @returns Updated content with new frontmatter
|
|
216
|
+
*/
|
|
217
|
+
private replaceFrontmatter(content: string, newFrontmatter: string): string {
|
|
218
|
+
const hasFrontmatter = /^---\n[\s\S]*?\n---/.test(content)
|
|
219
|
+
|
|
220
|
+
if (hasFrontmatter) {
|
|
221
|
+
return content.replace(/^---\n[\s\S]*?\n---/, newFrontmatter.trim())
|
|
222
|
+
} else {
|
|
223
|
+
return `${newFrontmatter}${content}`
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Gets the currently active route from plugin settings
|
|
229
|
+
* @returns The active route configuration or undefined
|
|
230
|
+
*/
|
|
231
|
+
private getCurrentRoute(): RouteConfig | undefined {
|
|
232
|
+
return this.plugin.settings.routes.find(route => route.enabled)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Main method to generate frontmatter based on file content and schema
|
|
237
|
+
* @param file - The file to process
|
|
238
|
+
* @param app - The Obsidian app instance
|
|
239
|
+
* @returns Promise<string> Generated frontmatter
|
|
240
|
+
*/
|
|
241
|
+
async generateFrontmatter(file: TFile, app: App): Promise<string> {
|
|
242
|
+
const content = await app.vault.read(file)
|
|
243
|
+
const currentRoute = this.getCurrentRoute()
|
|
244
|
+
|
|
245
|
+
if (!currentRoute?.generatedConfig) {
|
|
246
|
+
throw new Error('Generated configuration not found')
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Parse configuration and generate frontmatter
|
|
250
|
+
const config: GeneratedConfig = JSON.parse(currentRoute.generatedConfig)
|
|
251
|
+
|
|
252
|
+
const { text } = await generateText({
|
|
253
|
+
model: this.model,
|
|
254
|
+
system:
|
|
255
|
+
'You are an author writing frontmatter for a new document, we are using Obsidian, and we need the correct format for the data given by the user.' +
|
|
256
|
+
'Think about the data structure and the fields that are required for the frontmatter. The data should be in YAML format and follow the schema provided by the user.' +
|
|
257
|
+
'And think about SEO to include the right metadata for the document.',
|
|
258
|
+
prompt: this.buildPrompt(content, config),
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const cleanedText = await this.formatAndValidateYAML(
|
|
262
|
+
this.cleanGPTOutput(text),
|
|
263
|
+
config,
|
|
264
|
+
currentRoute.language
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return cleanedText
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Cleans the GPT output by removing code block markers
|
|
272
|
+
* @param text - The raw GPT output
|
|
273
|
+
* @returns Cleaned YAML content
|
|
274
|
+
*/
|
|
275
|
+
private cleanGPTOutput(text: string): string {
|
|
276
|
+
return text
|
|
277
|
+
.replace(/^```ya?ml\n?/i, '') // Remove starting ```yaml or ```yml
|
|
278
|
+
.replace(/```$/, '') // Remove ending ```
|
|
279
|
+
.trim() // Remove extra whitespace
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async formatAndValidateYAML(
|
|
283
|
+
initialText: string,
|
|
284
|
+
config: GeneratedConfig,
|
|
285
|
+
language: string
|
|
286
|
+
): Promise<string> {
|
|
287
|
+
const formattingPrompt = `As a YAML formatting expert, review and clean this frontmatter content.
|
|
288
|
+
Here are the specific requirements:
|
|
289
|
+
|
|
290
|
+
1. Format Validation:
|
|
291
|
+
- Remove any unnecessary blank lines
|
|
292
|
+
- Ensure consistent indentation (2 spaces)
|
|
293
|
+
- Maintain compact array formatting
|
|
294
|
+
- Verify all strings are properly quoted
|
|
295
|
+
|
|
296
|
+
2. Content Validation:
|
|
297
|
+
- All URLs should be valid format
|
|
298
|
+
- Tags should be compact without empty lines
|
|
299
|
+
- Array structures should be consistent
|
|
300
|
+
- Remove any suspicious or placeholder content
|
|
301
|
+
|
|
302
|
+
3. Structure Requirements:
|
|
303
|
+
- Keep fields in logical order
|
|
304
|
+
- Ensure all required fields are present
|
|
305
|
+
- Verify data types match schema
|
|
306
|
+
- Check language consistency
|
|
307
|
+
|
|
308
|
+
4. Stringified Context from the user :
|
|
309
|
+
content field: ${JSON.stringify(config.contentField)}
|
|
310
|
+
field mapping: ${JSON.stringify(config.fieldMappings)}
|
|
311
|
+
|
|
312
|
+
Original frontmatter to clean:
|
|
313
|
+
${initialText}
|
|
314
|
+
|
|
315
|
+
Return only the cleaned YAML frontmatter, maintaining exact and correct formatting. No explanation needed.
|
|
316
|
+
- Ensure locale matches the content language (if specified) "${language}"
|
|
317
|
+
- if the frontmatter is not in the correct language, please change it to "${language}"
|
|
318
|
+
- TRANSLATE EVERYTHING TO "${language}"`
|
|
319
|
+
|
|
320
|
+
const { text } = await generateText({
|
|
321
|
+
model: this.model,
|
|
322
|
+
system:
|
|
323
|
+
'You are a YAML formatting expert. Your only task is to clean and validate YAML frontmatter. Return only the cleaned YAML with no additional comments or explanations.',
|
|
324
|
+
prompt: formattingPrompt,
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
return this.cleanGPTOutput(text)
|
|
328
|
+
}
|
|
329
|
+
}
|