nca-ai-cms-astro-plugin 1.0.4 → 1.0.6
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 +23 -10
- package/package.json +1 -1
- package/src/api/prompts.ts +28 -2
- package/src/components/editor/SettingsTab.tsx +161 -6
- package/src/components/editor/styles.ts +84 -0
- package/src/index.ts +21 -1
- package/src/services/PromptService.ts +14 -0
- package/task.md +77 -0
- package/update.md +29 -0
package/README.md
CHANGED
|
@@ -16,13 +16,14 @@ An Astro plugin that adds an AI-powered content editor to your site. Generate ar
|
|
|
16
16
|
### 1. Install
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
npm install nca-ai-cms-astro-plugin
|
|
19
|
+
npm install nca-ai-cms-astro-plugin @astrojs/node @astrojs/react @astrojs/db react react-dom @google/genai @google/generative-ai gray-matter marked sanitize-html sharp turndown zod
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
### 2. Add to your Astro config
|
|
23
23
|
|
|
24
|
-
```
|
|
24
|
+
```js
|
|
25
25
|
// astro.config.mjs
|
|
26
|
+
import { defineConfig } from 'astro/config';
|
|
26
27
|
import ncaAiCms from 'nca-ai-cms-astro-plugin';
|
|
27
28
|
|
|
28
29
|
export default defineConfig({
|
|
@@ -30,24 +31,36 @@ export default defineConfig({
|
|
|
30
31
|
});
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
The plugin auto-registers `react()`, `db()`, `output: 'server'`, and the `@astrojs/node` adapter. You can still set them manually if you need custom options.
|
|
35
|
+
|
|
36
|
+
### 3. Create `.env.local`
|
|
34
37
|
|
|
35
38
|
```env
|
|
39
|
+
EDITOR_ADMIN=admin
|
|
40
|
+
EDITOR_PASSWORD=your-secure-password
|
|
36
41
|
GOOGLE_GEMINI_API_KEY=your-gemini-api-key
|
|
37
|
-
EDITOR_ADMIN=your-username
|
|
38
|
-
EDITOR_PASSWORD=your-password
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
| Variable | Required |
|
|
44
|
+
| Variable | Required | Purpose |
|
|
42
45
|
|---|---|---|
|
|
46
|
+
| `EDITOR_ADMIN` | Yes | Login username for the editor |
|
|
47
|
+
| `EDITOR_PASSWORD` | Yes | Login password for the editor |
|
|
43
48
|
| `GOOGLE_GEMINI_API_KEY` | Yes | Google Gemini API key for content and image generation |
|
|
44
|
-
| `EDITOR_ADMIN` | Yes | Username for editor login |
|
|
45
|
-
| `EDITOR_PASSWORD` | Yes | Password for editor login |
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
Add `.env.local` to your `.gitignore` — never commit secrets.
|
|
51
|
+
|
|
52
|
+
### 4. Start the dev server
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx astro dev
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- Login: http://localhost:4321/login
|
|
59
|
+
- Editor: http://localhost:4321/editor (redirects to login if not authenticated)
|
|
60
|
+
|
|
61
|
+
### Requirements
|
|
48
62
|
|
|
49
63
|
- Astro 5+
|
|
50
|
-
- `@astrojs/db` and `@astrojs/react` integrations
|
|
51
64
|
- Node.js 18+
|
|
52
65
|
|
|
53
66
|
## Options
|
package/package.json
CHANGED
package/src/api/prompts.ts
CHANGED
|
@@ -22,24 +22,50 @@ export const GET: APIRoute = async () => {
|
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
// POST /api/prompts -
|
|
25
|
+
// POST /api/prompts - Create or update a prompt or setting
|
|
26
26
|
export const POST: APIRoute = async ({ request }) => {
|
|
27
27
|
try {
|
|
28
28
|
const data = await request.json();
|
|
29
29
|
|
|
30
|
+
// Create a new prompt
|
|
31
|
+
if (data.action === 'create' && data.name && data.category && data.promptText) {
|
|
32
|
+
const id = `${data.category}_${Date.now()}`;
|
|
33
|
+
await service.createPrompt(id, data.name, data.category, data.promptText);
|
|
34
|
+
return jsonResponse({ success: true, type: 'prompt', id });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Update an existing prompt
|
|
30
38
|
if (data.type === 'prompt' && data.id && data.promptText !== undefined) {
|
|
31
39
|
await service.updatePrompt(data.id, data.promptText);
|
|
32
40
|
return jsonResponse({ success: true, type: 'prompt', id: data.id });
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
// Update a setting
|
|
35
44
|
if (data.type === 'setting' && data.key && data.value !== undefined) {
|
|
36
45
|
await service.updateSetting(data.key, data.value);
|
|
37
46
|
return jsonResponse({ success: true, type: 'setting', key: data.key });
|
|
38
47
|
}
|
|
39
48
|
|
|
40
|
-
return jsonError('Invalid request: missing
|
|
49
|
+
return jsonError('Invalid request: missing required fields', 400);
|
|
41
50
|
} catch (error) {
|
|
42
51
|
console.error('Update prompt error:', error);
|
|
43
52
|
return jsonError(error);
|
|
44
53
|
}
|
|
45
54
|
};
|
|
55
|
+
|
|
56
|
+
// DELETE /api/prompts - Delete a prompt
|
|
57
|
+
export const DELETE: APIRoute = async ({ request }) => {
|
|
58
|
+
try {
|
|
59
|
+
const data = await request.json();
|
|
60
|
+
|
|
61
|
+
if (!data.id) {
|
|
62
|
+
return jsonError('Missing prompt id', 400);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await service.deletePrompt(data.id);
|
|
66
|
+
return jsonResponse({ success: true, id: data.id });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('Delete prompt error:', error);
|
|
69
|
+
return jsonError(error);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
@@ -10,11 +10,44 @@ const SUB_TABS: { key: SettingsSubTab; label: string }[] = [
|
|
|
10
10
|
{ key: 'website', label: 'Website' },
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
+
const CATEGORY_GUIDES: Record<SettingsSubTab, { title: string; description: string; example: string }> = {
|
|
14
|
+
'homepage': {
|
|
15
|
+
title: 'Homepage-Einstellungen',
|
|
16
|
+
description: 'Definiere hier Prompts, die das Erscheinungsbild und den Inhalt deiner Startseite steuern. Gute Prompts beschreiben Zielgruppe, Tonalitaet und zentrale Botschaft.',
|
|
17
|
+
example: 'Beispiel: "Schreibe einen Hero-Text fuer eine Webdesign-Agentur. Zielgruppe: KMU in Deutschland. Ton: professionell, nahbar. Kernbotschaft: Barrierefreies Webdesign als Wettbewerbsvorteil."',
|
|
18
|
+
},
|
|
19
|
+
'content-ai': {
|
|
20
|
+
title: 'Content-KI Prompts',
|
|
21
|
+
description: 'Steuere hier, wie die KI Blogartikel und Texte generiert. Je praeziser der Prompt, desto besser das Ergebnis. Definiere Schreibstil, SEO-Keywords und inhaltliche Schwerpunkte.',
|
|
22
|
+
example: 'Beispiel: "Schreibe einen Fachartikel mit 800-1200 Woertern. Verwende die Keywords [KEYWORD] natuerlich im Text. Struktur: Einleitung mit Hook, 3-4 Abschnitte mit H2, Fazit mit CTA. Ton: fachlich aber verstaendlich."',
|
|
23
|
+
},
|
|
24
|
+
'analysis-ai': {
|
|
25
|
+
title: 'Analyse-KI Prompts',
|
|
26
|
+
description: 'Konfiguriere Prompts fuer die inhaltliche Analyse bestehender Texte. Die KI kann Texte auf SEO, Lesbarkeit, Barrierefreiheit und Content-Qualitaet pruefen.',
|
|
27
|
+
example: 'Beispiel: "Analysiere den Text auf SEO-Optimierung. Pruefe: Keyword-Dichte, Meta-Beschreibung, Ueberschriften-Hierarchie, interne Verlinkung. Gib konkrete Verbesserungsvorschlaege."',
|
|
28
|
+
},
|
|
29
|
+
'image-ai': {
|
|
30
|
+
title: 'Bild-KI Prompts',
|
|
31
|
+
description: 'Definiere Prompts fuer die KI-Bildgenerierung. Beschreibe Stil, Farbpalette und Bildkomposition fuer konsistente visuelle Inhalte.',
|
|
32
|
+
example: 'Beispiel: "Erstelle ein Blog-Header-Bild im minimalistischen Flat-Design. Farbpalette: Dunkelblau (#1a365d), Weiss, Akzent-Rot (#e63946). Kein Text im Bild. Seitenverhaeltnis 16:9."',
|
|
33
|
+
},
|
|
34
|
+
'website': {
|
|
35
|
+
title: 'Website-Einstellungen',
|
|
36
|
+
description: 'Allgemeine Einstellungen fuer deine Website: CTA-Texte, Standard-Tags, Markenrichtlinien und wiederkehrende Textbausteine.',
|
|
37
|
+
example: 'Beispiel: "Standard-CTA: Jetzt kostenlos beraten lassen. Core-Tags: Barrierefreiheit, Webdesign, SEO, WCAG. Markensprache: Sie-Anrede, professionell, loesungsorientiert."',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
13
41
|
interface EditState {
|
|
14
42
|
id: string;
|
|
15
43
|
value: string;
|
|
16
44
|
}
|
|
17
45
|
|
|
46
|
+
interface AddFormState {
|
|
47
|
+
name: string;
|
|
48
|
+
promptText: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
18
51
|
export function SettingsTab() {
|
|
19
52
|
const [activeSubTab, setActiveSubTab] =
|
|
20
53
|
useState<SettingsSubTab>('homepage');
|
|
@@ -23,6 +56,9 @@ export function SettingsTab() {
|
|
|
23
56
|
const [error, setError] = useState<string | null>(null);
|
|
24
57
|
const [editing, setEditing] = useState<EditState | null>(null);
|
|
25
58
|
const [saving, setSaving] = useState(false);
|
|
59
|
+
const [showAddForm, setShowAddForm] = useState(false);
|
|
60
|
+
const [addForm, setAddForm] = useState<AddFormState>({ name: '', promptText: '' });
|
|
61
|
+
const [deleting, setDeleting] = useState<string | null>(null);
|
|
26
62
|
|
|
27
63
|
const loadPrompts = useCallback(async () => {
|
|
28
64
|
setLoading(true);
|
|
@@ -30,8 +66,8 @@ export function SettingsTab() {
|
|
|
30
66
|
try {
|
|
31
67
|
const res = await fetch('/api/prompts');
|
|
32
68
|
if (!res.ok) throw new Error('Fehler beim Laden der Einstellungen');
|
|
33
|
-
const data =
|
|
34
|
-
setPrompts(data);
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
setPrompts(Array.isArray(data.prompts) ? data.prompts : []);
|
|
35
71
|
} catch (err) {
|
|
36
72
|
setError(
|
|
37
73
|
err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten',
|
|
@@ -86,6 +122,57 @@ export function SettingsTab() {
|
|
|
86
122
|
}
|
|
87
123
|
};
|
|
88
124
|
|
|
125
|
+
const handleAdd = async () => {
|
|
126
|
+
if (!addForm.name.trim() || !addForm.promptText.trim()) return;
|
|
127
|
+
setSaving(true);
|
|
128
|
+
setError(null);
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetch('/api/prompts', {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: { 'Content-Type': 'application/json' },
|
|
133
|
+
body: JSON.stringify({
|
|
134
|
+
action: 'create',
|
|
135
|
+
name: addForm.name,
|
|
136
|
+
category: activeSubTab,
|
|
137
|
+
promptText: addForm.promptText,
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok) throw new Error('Fehler beim Erstellen');
|
|
141
|
+
setAddForm({ name: '', promptText: '' });
|
|
142
|
+
setShowAddForm(false);
|
|
143
|
+
await loadPrompts();
|
|
144
|
+
} catch (err) {
|
|
145
|
+
setError(
|
|
146
|
+
err instanceof Error ? err.message : 'Fehler beim Erstellen',
|
|
147
|
+
);
|
|
148
|
+
} finally {
|
|
149
|
+
setSaving(false);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const handleDelete = async (id: string) => {
|
|
154
|
+
setDeleting(id);
|
|
155
|
+
setError(null);
|
|
156
|
+
try {
|
|
157
|
+
const res = await fetch('/api/prompts', {
|
|
158
|
+
method: 'DELETE',
|
|
159
|
+
headers: { 'Content-Type': 'application/json' },
|
|
160
|
+
body: JSON.stringify({ id }),
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok) throw new Error('Fehler beim Loeschen');
|
|
163
|
+
setPrompts((prev) => prev.filter((p) => p.id !== id));
|
|
164
|
+
if (editing?.id === id) setEditing(null);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
setError(
|
|
167
|
+
err instanceof Error ? err.message : 'Fehler beim Loeschen',
|
|
168
|
+
);
|
|
169
|
+
} finally {
|
|
170
|
+
setDeleting(null);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const guide = CATEGORY_GUIDES[activeSubTab];
|
|
175
|
+
|
|
89
176
|
return (
|
|
90
177
|
<div style={styles.settingsContent}>
|
|
91
178
|
<div style={styles.subTabNav} role="tablist">
|
|
@@ -103,6 +190,7 @@ export function SettingsTab() {
|
|
|
103
190
|
onClick={() => {
|
|
104
191
|
setActiveSubTab(tab.key);
|
|
105
192
|
setEditing(null);
|
|
193
|
+
setShowAddForm(false);
|
|
106
194
|
}}
|
|
107
195
|
>
|
|
108
196
|
{tab.label}
|
|
@@ -116,9 +204,11 @@ export function SettingsTab() {
|
|
|
116
204
|
|
|
117
205
|
{error && <div style={styles.error}>{error}</div>}
|
|
118
206
|
|
|
119
|
-
{!loading && filteredPrompts.length === 0 && (
|
|
120
|
-
<div style={styles.
|
|
121
|
-
|
|
207
|
+
{!loading && filteredPrompts.length === 0 && !showAddForm && (
|
|
208
|
+
<div style={styles.emptyGuide}>
|
|
209
|
+
<div style={styles.emptyGuideTitle}>{guide.title}</div>
|
|
210
|
+
<div style={styles.emptyGuideText}>{guide.description}</div>
|
|
211
|
+
<div style={styles.emptyGuideExample}>{guide.example}</div>
|
|
122
212
|
</div>
|
|
123
213
|
)}
|
|
124
214
|
|
|
@@ -165,7 +255,7 @@ export function SettingsTab() {
|
|
|
165
255
|
<div style={styles.settingsItemValue}>
|
|
166
256
|
{prompt.promptText}
|
|
167
257
|
</div>
|
|
168
|
-
<div style={{ padding: '0 1rem 1rem' }}>
|
|
258
|
+
<div style={{ padding: '0 1rem 1rem', display: 'flex', gap: '0.5rem' }}>
|
|
169
259
|
<button
|
|
170
260
|
type="button"
|
|
171
261
|
style={styles.editButton}
|
|
@@ -173,6 +263,14 @@ export function SettingsTab() {
|
|
|
173
263
|
>
|
|
174
264
|
Bearbeiten
|
|
175
265
|
</button>
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
style={styles.deleteButton}
|
|
269
|
+
onClick={() => handleDelete(prompt.id)}
|
|
270
|
+
disabled={deleting === prompt.id}
|
|
271
|
+
>
|
|
272
|
+
{deleting === prompt.id ? 'Loesche...' : 'Loeschen'}
|
|
273
|
+
</button>
|
|
176
274
|
</div>
|
|
177
275
|
</>
|
|
178
276
|
)}
|
|
@@ -180,6 +278,63 @@ export function SettingsTab() {
|
|
|
180
278
|
))}
|
|
181
279
|
</div>
|
|
182
280
|
)}
|
|
281
|
+
|
|
282
|
+
{!loading && showAddForm && (
|
|
283
|
+
<div style={styles.addForm}>
|
|
284
|
+
<div style={styles.addFormTitle}>Neuen Prompt hinzufuegen ({guide.title})</div>
|
|
285
|
+
<div style={styles.addFormField}>
|
|
286
|
+
<label style={styles.addFormLabel}>Name</label>
|
|
287
|
+
<input
|
|
288
|
+
style={styles.addFormInput}
|
|
289
|
+
type="text"
|
|
290
|
+
placeholder="z.B. Blog-Artikel Prompt, SEO-Analyse, Hero-Text"
|
|
291
|
+
value={addForm.name}
|
|
292
|
+
onChange={(e) => setAddForm({ ...addForm, name: e.target.value })}
|
|
293
|
+
/>
|
|
294
|
+
</div>
|
|
295
|
+
<div style={styles.addFormField}>
|
|
296
|
+
<label style={styles.addFormLabel}>Prompt-Text</label>
|
|
297
|
+
<textarea
|
|
298
|
+
style={styles.textarea}
|
|
299
|
+
placeholder={guide.example}
|
|
300
|
+
value={addForm.promptText}
|
|
301
|
+
onChange={(e) => setAddForm({ ...addForm, promptText: e.target.value })}
|
|
302
|
+
/>
|
|
303
|
+
</div>
|
|
304
|
+
<div style={styles.editButtons}>
|
|
305
|
+
<button
|
|
306
|
+
type="button"
|
|
307
|
+
style={styles.addButton}
|
|
308
|
+
onClick={handleAdd}
|
|
309
|
+
disabled={saving || !addForm.name.trim() || !addForm.promptText.trim()}
|
|
310
|
+
>
|
|
311
|
+
{saving ? 'Erstelle...' : 'Erstellen'}
|
|
312
|
+
</button>
|
|
313
|
+
<button
|
|
314
|
+
type="button"
|
|
315
|
+
style={styles.cancelButton}
|
|
316
|
+
onClick={() => {
|
|
317
|
+
setShowAddForm(false);
|
|
318
|
+
setAddForm({ name: '', promptText: '' });
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
Abbrechen
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
)}
|
|
326
|
+
|
|
327
|
+
{!loading && !showAddForm && (
|
|
328
|
+
<div style={{ textAlign: 'center' as const, padding: '1rem 0' }}>
|
|
329
|
+
<button
|
|
330
|
+
type="button"
|
|
331
|
+
style={styles.addButton}
|
|
332
|
+
onClick={() => setShowAddForm(true)}
|
|
333
|
+
>
|
|
334
|
+
+ Neuen Prompt hinzufuegen
|
|
335
|
+
</button>
|
|
336
|
+
</div>
|
|
337
|
+
)}
|
|
183
338
|
</div>
|
|
184
339
|
);
|
|
185
340
|
}
|
|
@@ -594,4 +594,88 @@ export const styles: Record<string, React.CSSProperties> = {
|
|
|
594
594
|
color: textMuted,
|
|
595
595
|
fontSize: '0.95rem',
|
|
596
596
|
},
|
|
597
|
+
emptyGuide: {
|
|
598
|
+
padding: '2rem',
|
|
599
|
+
background: surfaceElevated,
|
|
600
|
+
border: `1px solid ${border}`,
|
|
601
|
+
borderRadius: '12px',
|
|
602
|
+
marginBottom: '1.5rem',
|
|
603
|
+
},
|
|
604
|
+
emptyGuideTitle: {
|
|
605
|
+
fontSize: '1.1rem',
|
|
606
|
+
fontWeight: 600,
|
|
607
|
+
color: text,
|
|
608
|
+
marginBottom: '0.75rem',
|
|
609
|
+
fontFamily: fontDisplay,
|
|
610
|
+
},
|
|
611
|
+
emptyGuideText: {
|
|
612
|
+
fontSize: '0.9rem',
|
|
613
|
+
color: textMuted,
|
|
614
|
+
lineHeight: 1.6,
|
|
615
|
+
marginBottom: '0.5rem',
|
|
616
|
+
},
|
|
617
|
+
emptyGuideExample: {
|
|
618
|
+
background: bg,
|
|
619
|
+
border: `1px solid ${border}`,
|
|
620
|
+
borderRadius: '8px',
|
|
621
|
+
padding: '0.75rem 1rem',
|
|
622
|
+
marginTop: '0.75rem',
|
|
623
|
+
fontSize: '0.85rem',
|
|
624
|
+
fontFamily: fontMono,
|
|
625
|
+
color: textAccent,
|
|
626
|
+
lineHeight: 1.5,
|
|
627
|
+
},
|
|
628
|
+
addForm: {
|
|
629
|
+
padding: '1.5rem',
|
|
630
|
+
background: surface,
|
|
631
|
+
border: `1px solid ${border}`,
|
|
632
|
+
borderRadius: '12px',
|
|
633
|
+
},
|
|
634
|
+
addFormTitle: {
|
|
635
|
+
fontSize: '1rem',
|
|
636
|
+
fontWeight: 600,
|
|
637
|
+
color: text,
|
|
638
|
+
marginBottom: '1rem',
|
|
639
|
+
},
|
|
640
|
+
addFormField: {
|
|
641
|
+
marginBottom: '0.75rem',
|
|
642
|
+
},
|
|
643
|
+
addFormLabel: {
|
|
644
|
+
display: 'block',
|
|
645
|
+
fontSize: '0.8rem',
|
|
646
|
+
color: textMuted,
|
|
647
|
+
marginBottom: '0.25rem',
|
|
648
|
+
fontWeight: 500,
|
|
649
|
+
},
|
|
650
|
+
addFormInput: {
|
|
651
|
+
width: '100%',
|
|
652
|
+
background: bg,
|
|
653
|
+
border: `1px solid ${border}`,
|
|
654
|
+
borderRadius: '8px',
|
|
655
|
+
padding: '0.5rem 0.75rem',
|
|
656
|
+
color: text,
|
|
657
|
+
fontSize: '0.85rem',
|
|
658
|
+
outline: 'none',
|
|
659
|
+
boxSizing: 'border-box' as const,
|
|
660
|
+
},
|
|
661
|
+
deleteButton: {
|
|
662
|
+
background: 'transparent',
|
|
663
|
+
border: `1px solid ${error}`,
|
|
664
|
+
color: error,
|
|
665
|
+
padding: '0.4rem 0.8rem',
|
|
666
|
+
borderRadius: '6px',
|
|
667
|
+
fontSize: '0.8rem',
|
|
668
|
+
fontWeight: 500,
|
|
669
|
+
cursor: 'pointer',
|
|
670
|
+
},
|
|
671
|
+
addButton: {
|
|
672
|
+
background: primary,
|
|
673
|
+
border: 'none',
|
|
674
|
+
color: '#fff',
|
|
675
|
+
padding: '0.5rem 1rem',
|
|
676
|
+
borderRadius: '8px',
|
|
677
|
+
fontSize: '0.85rem',
|
|
678
|
+
fontWeight: 600,
|
|
679
|
+
cursor: 'pointer',
|
|
680
|
+
},
|
|
597
681
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { AstroIntegration } from 'astro';
|
|
2
|
+
import react from '@astrojs/react';
|
|
3
|
+
import db from '@astrojs/db';
|
|
4
|
+
import node from '@astrojs/node';
|
|
2
5
|
|
|
3
6
|
export interface NcaAiCmsPluginOptions {
|
|
4
7
|
contentPath?: string;
|
|
@@ -20,12 +23,29 @@ export default function ncaAiCms(
|
|
|
20
23
|
});
|
|
21
24
|
},
|
|
22
25
|
|
|
23
|
-
'astro:config:setup'({ injectRoute, updateConfig, addMiddleware }) {
|
|
26
|
+
'astro:config:setup'({ injectRoute, updateConfig, addMiddleware, config }) {
|
|
24
27
|
addMiddleware({
|
|
25
28
|
entrypoint: 'nca-ai-cms-astro-plugin/middleware.ts',
|
|
26
29
|
order: 'pre',
|
|
27
30
|
});
|
|
28
31
|
|
|
32
|
+
// Auto-register react and db integrations if not already present
|
|
33
|
+
const hasReact = config.integrations.some((i) => i.name === '@astrojs/react');
|
|
34
|
+
if (!hasReact) {
|
|
35
|
+
config.integrations.push(react());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const hasDb = config.integrations.some((i) => i.name === '@astrojs/db');
|
|
39
|
+
if (!hasDb) {
|
|
40
|
+
config.integrations.push(...(db() as unknown as AstroIntegration[]));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Auto-configure server output and node adapter
|
|
44
|
+
updateConfig({
|
|
45
|
+
output: 'server' as const,
|
|
46
|
+
adapter: node({ mode: 'standalone' }),
|
|
47
|
+
});
|
|
48
|
+
|
|
29
49
|
// Virtual module for config sharing
|
|
30
50
|
updateConfig({
|
|
31
51
|
vite: {
|
|
@@ -16,6 +16,20 @@ export class PromptService {
|
|
|
16
16
|
return result?.promptText ?? null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
async createPrompt(id: string, name: string, category: string, promptText: string): Promise<void> {
|
|
20
|
+
await db.insert(Prompts).values({
|
|
21
|
+
id,
|
|
22
|
+
name,
|
|
23
|
+
category,
|
|
24
|
+
promptText,
|
|
25
|
+
updatedAt: new Date(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async deletePrompt(id: string): Promise<void> {
|
|
30
|
+
await db.delete(Prompts).where(eq(Prompts.id, id));
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
async updatePrompt(id: string, text: string): Promise<void> {
|
|
20
34
|
await db
|
|
21
35
|
.update(Prompts)
|
package/task.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Fix nca-ai-cms-astro-plugin v1.0.5
|
|
2
|
+
|
|
3
|
+
## 1. Fix SettingsTab.tsx response parsing
|
|
4
|
+
|
|
5
|
+
**File:** `src/components/editor/SettingsTab.tsx`
|
|
6
|
+
|
|
7
|
+
Line 33-34: API `/api/prompts` returns `{ prompts, settings }` but the component casts the entire response as `Prompt[]`, causing a crash.
|
|
8
|
+
|
|
9
|
+
```diff
|
|
10
|
+
- const data = (await res.json()) as Prompt[];
|
|
11
|
+
- setPrompts(data);
|
|
12
|
+
+ const data = await res.json();
|
|
13
|
+
+ setPrompts(Array.isArray(data.prompts) ? data.prompts : []);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 2. Auto-register dependencies in index.ts
|
|
17
|
+
|
|
18
|
+
**File:** `src/index.ts`
|
|
19
|
+
|
|
20
|
+
The consumer currently must manually add `react()`, `db()`, `output: 'server'`, and the node adapter. The plugin should handle this automatically in `astro:config:setup`.
|
|
21
|
+
|
|
22
|
+
- Import `@astrojs/react`, `@astrojs/db`, `@astrojs/node` at the top
|
|
23
|
+
- Add `addIntegration` and `config` to the hook destructuring
|
|
24
|
+
- Check `config.integrations` for existing react/db before adding
|
|
25
|
+
- Call `updateConfig()` with `output: 'server'` and `adapter: node({ mode: 'standalone' })`
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
'astro:config:setup'({ injectRoute, updateConfig, addMiddleware, addIntegration, config }) {
|
|
29
|
+
const hasReact = config.integrations.some((i) => i.name === '@astrojs/react');
|
|
30
|
+
if (!hasReact) {
|
|
31
|
+
addIntegration(react());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const hasDb = config.integrations.some((i) => i.name === '@astrojs/db');
|
|
35
|
+
if (!hasDb) {
|
|
36
|
+
addIntegration(db());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
updateConfig({
|
|
40
|
+
output: 'server' as const,
|
|
41
|
+
adapter: node({ mode: 'standalone' }),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ... rest of hook unchanged
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 3. Bump version to 1.0.5
|
|
49
|
+
|
|
50
|
+
**File:** `package.json`
|
|
51
|
+
|
|
52
|
+
Change `"version": "1.0.4"` to `"version": "1.0.5"`.
|
|
53
|
+
|
|
54
|
+
## 4. Update README with simplified setup
|
|
55
|
+
|
|
56
|
+
**File:** `README.md`
|
|
57
|
+
|
|
58
|
+
Update setup instructions to reflect that the consumer only needs:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import ncaAiCms from 'nca-ai-cms-astro-plugin';
|
|
62
|
+
|
|
63
|
+
export default defineConfig({
|
|
64
|
+
integrations: [ncaAiCms()],
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Add `.env.local` step with `GOOGLE_GEMINI_API_KEY`, `EDITOR_ADMIN`, `EDITOR_PASSWORD`.
|
|
69
|
+
|
|
70
|
+
Note that `prerender: false` is already present on all 18 `injectRoute()` calls — no changes needed there.
|
|
71
|
+
|
|
72
|
+
## Verification
|
|
73
|
+
|
|
74
|
+
1. In a test project, use only `integrations: [ncaAiCms()]` in astro config
|
|
75
|
+
2. Run `npx astro dev`
|
|
76
|
+
3. Open `/login` — should render without errors
|
|
77
|
+
4. Log in, open `/editor` — settings tab should load without crash
|
package/update.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
# v1.0.6
|
|
2
|
+
|
|
3
|
+
## SettingsTab: category guides and custom prompts
|
|
4
|
+
- Empty categories now show content marketing guidance with concrete examples per tab (Homepage, Content-KI, Analyse-KI, Bild-KI, Website)
|
|
5
|
+
- New "+ Neuen Prompt hinzufuegen" button to create custom prompts directly in the UI
|
|
6
|
+
- Each prompt card now has a delete button
|
|
7
|
+
- API: added POST with `action: create` and DELETE endpoint for prompts
|
|
8
|
+
- PromptService: added `createPrompt()` and `deletePrompt()` methods
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# v1.0.5
|
|
13
|
+
|
|
14
|
+
## Fix: SettingsTab crash on prompts response
|
|
15
|
+
- `/api/prompts` returns `{ prompts, settings }` but SettingsTab cast the entire response as `Prompt[]`
|
|
16
|
+
- Fixed to extract `data.prompts` from the response object
|
|
17
|
+
|
|
18
|
+
## Auto-register dependencies
|
|
19
|
+
- Plugin now auto-configures `output: 'server'`, `@astrojs/node` adapter, `react()`, and `db()` via `updateConfig()`
|
|
20
|
+
- Consumer config is now just `integrations: [ncaAiCms()]`
|
|
21
|
+
- Existing manual config is respected — auto-registration only kicks in when not already set
|
|
22
|
+
|
|
23
|
+
## Setup guide in README
|
|
24
|
+
- Full install command with all peer dependencies
|
|
25
|
+
- `.env.local` setup with variable reference table
|
|
26
|
+
- Minimal `astro.config.mjs` example
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
1
30
|
# v1.0.4
|
|
2
31
|
|
|
3
32
|
## Fix: Environment variables and route prerendering
|