nca-ai-cms-astro-plugin 1.0.6 → 1.0.7
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/package.json +1 -1
- package/src/components/editor/SettingsTab.tsx +238 -125
- package/task.md +81 -4
- package/update.md +9 -3
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
-
import type { Prompt, SettingsSubTab } from './types';
|
|
2
|
+
import type { Prompt, Setting, SettingsSubTab } from './types';
|
|
3
3
|
import { styles } from './styles';
|
|
4
4
|
|
|
5
5
|
const SUB_TABS: { key: SettingsSubTab; label: string }[] = [
|
|
@@ -10,11 +10,30 @@ const SUB_TABS: { key: SettingsSubTab; label: string }[] = [
|
|
|
10
10
|
{ key: 'website', label: 'Website' },
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
+
const SETTINGS_TABS: SettingsSubTab[] = ['homepage', 'website'];
|
|
14
|
+
|
|
15
|
+
const SETTINGS_FIELDS: Record<string, { key: string; label: string; type: 'input' | 'textarea' }[]> = {
|
|
16
|
+
homepage: [
|
|
17
|
+
{ key: 'hero_headline', label: 'Hero Ueberschrift', type: 'input' },
|
|
18
|
+
{ key: 'hero_text', label: 'Hero Text', type: 'textarea' },
|
|
19
|
+
{ key: 'target_audience', label: 'Zielgruppe', type: 'input' },
|
|
20
|
+
{ key: 'tone', label: 'Tonalitaet', type: 'input' },
|
|
21
|
+
{ key: 'core_message', label: 'Kernbotschaft', type: 'textarea' },
|
|
22
|
+
],
|
|
23
|
+
website: [
|
|
24
|
+
{ key: 'cta_url', label: 'CTA Link', type: 'input' },
|
|
25
|
+
{ key: 'cta_style', label: 'CTA Stil', type: 'input' },
|
|
26
|
+
{ key: 'cta_prompt', label: 'CTA Prompt', type: 'textarea' },
|
|
27
|
+
{ key: 'core_tags', label: 'Core Tags (kommagetrennt)', type: 'input' },
|
|
28
|
+
{ key: 'brand_guidelines', label: 'Markenrichtlinien', type: 'textarea' },
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
|
|
13
32
|
const CATEGORY_GUIDES: Record<SettingsSubTab, { title: string; description: string; example: string }> = {
|
|
14
33
|
'homepage': {
|
|
15
34
|
title: 'Homepage-Einstellungen',
|
|
16
|
-
description: '
|
|
17
|
-
example: '
|
|
35
|
+
description: 'Konfiguriere hier die zentralen Inhalte deiner Startseite: Hero-Text, Zielgruppe, Tonalitaet und Kernbotschaft.',
|
|
36
|
+
example: '',
|
|
18
37
|
},
|
|
19
38
|
'content-ai': {
|
|
20
39
|
title: 'Content-KI Prompts',
|
|
@@ -33,11 +52,15 @@ const CATEGORY_GUIDES: Record<SettingsSubTab, { title: string; description: stri
|
|
|
33
52
|
},
|
|
34
53
|
'website': {
|
|
35
54
|
title: 'Website-Einstellungen',
|
|
36
|
-
description: 'Allgemeine Einstellungen fuer deine Website: CTA-Texte, Standard-Tags
|
|
37
|
-
example: '
|
|
55
|
+
description: 'Allgemeine Einstellungen fuer deine Website: CTA-Texte, Standard-Tags und Markenrichtlinien.',
|
|
56
|
+
example: '',
|
|
38
57
|
},
|
|
39
58
|
};
|
|
40
59
|
|
|
60
|
+
function isSettingsTab(tab: SettingsSubTab): boolean {
|
|
61
|
+
return SETTINGS_TABS.includes(tab);
|
|
62
|
+
}
|
|
63
|
+
|
|
41
64
|
interface EditState {
|
|
42
65
|
id: string;
|
|
43
66
|
value: string;
|
|
@@ -52,6 +75,9 @@ export function SettingsTab() {
|
|
|
52
75
|
const [activeSubTab, setActiveSubTab] =
|
|
53
76
|
useState<SettingsSubTab>('homepage');
|
|
54
77
|
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
|
78
|
+
const [_settings, setSettings] = useState<Setting[]>([]);
|
|
79
|
+
const [settingsForm, setSettingsForm] = useState<Record<string, string>>({});
|
|
80
|
+
const [settingsSaved, setSettingsSaved] = useState(false);
|
|
55
81
|
const [loading, setLoading] = useState(true);
|
|
56
82
|
const [error, setError] = useState<string | null>(null);
|
|
57
83
|
const [editing, setEditing] = useState<EditState | null>(null);
|
|
@@ -60,7 +86,7 @@ export function SettingsTab() {
|
|
|
60
86
|
const [addForm, setAddForm] = useState<AddFormState>({ name: '', promptText: '' });
|
|
61
87
|
const [deleting, setDeleting] = useState<string | null>(null);
|
|
62
88
|
|
|
63
|
-
const
|
|
89
|
+
const loadData = useCallback(async () => {
|
|
64
90
|
setLoading(true);
|
|
65
91
|
setError(null);
|
|
66
92
|
try {
|
|
@@ -68,6 +94,13 @@ export function SettingsTab() {
|
|
|
68
94
|
if (!res.ok) throw new Error('Fehler beim Laden der Einstellungen');
|
|
69
95
|
const data = await res.json();
|
|
70
96
|
setPrompts(Array.isArray(data.prompts) ? data.prompts : []);
|
|
97
|
+
const loadedSettings: Setting[] = Array.isArray(data.settings) ? data.settings : [];
|
|
98
|
+
setSettings(loadedSettings);
|
|
99
|
+
const formValues: Record<string, string> = {};
|
|
100
|
+
for (const s of loadedSettings) {
|
|
101
|
+
formValues[s.key] = s.value;
|
|
102
|
+
}
|
|
103
|
+
setSettingsForm(formValues);
|
|
71
104
|
} catch (err) {
|
|
72
105
|
setError(
|
|
73
106
|
err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten',
|
|
@@ -78,8 +111,8 @@ export function SettingsTab() {
|
|
|
78
111
|
}, []);
|
|
79
112
|
|
|
80
113
|
useEffect(() => {
|
|
81
|
-
|
|
82
|
-
}, [
|
|
114
|
+
loadData();
|
|
115
|
+
}, [loadData]);
|
|
83
116
|
|
|
84
117
|
const filteredPrompts = prompts.filter(
|
|
85
118
|
(p) => p.category === activeSubTab,
|
|
@@ -122,6 +155,32 @@ export function SettingsTab() {
|
|
|
122
155
|
}
|
|
123
156
|
};
|
|
124
157
|
|
|
158
|
+
const handleSettingsSave = async () => {
|
|
159
|
+
setSaving(true);
|
|
160
|
+
setError(null);
|
|
161
|
+
setSettingsSaved(false);
|
|
162
|
+
try {
|
|
163
|
+
const fields = SETTINGS_FIELDS[activeSubTab] ?? [];
|
|
164
|
+
for (const field of fields) {
|
|
165
|
+
const value = settingsForm[field.key] ?? '';
|
|
166
|
+
const res = await fetch('/api/prompts', {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: { 'Content-Type': 'application/json' },
|
|
169
|
+
body: JSON.stringify({ type: 'setting', key: field.key, value }),
|
|
170
|
+
});
|
|
171
|
+
if (!res.ok) throw new Error(`Fehler beim Speichern von "${field.label}"`);
|
|
172
|
+
}
|
|
173
|
+
setSettingsSaved(true);
|
|
174
|
+
setTimeout(() => setSettingsSaved(false), 3000);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
setError(
|
|
177
|
+
err instanceof Error ? err.message : 'Fehler beim Speichern',
|
|
178
|
+
);
|
|
179
|
+
} finally {
|
|
180
|
+
setSaving(false);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
125
184
|
const handleAdd = async () => {
|
|
126
185
|
if (!addForm.name.trim() || !addForm.promptText.trim()) return;
|
|
127
186
|
setSaving(true);
|
|
@@ -140,7 +199,7 @@ export function SettingsTab() {
|
|
|
140
199
|
if (!res.ok) throw new Error('Fehler beim Erstellen');
|
|
141
200
|
setAddForm({ name: '', promptText: '' });
|
|
142
201
|
setShowAddForm(false);
|
|
143
|
-
await
|
|
202
|
+
await loadData();
|
|
144
203
|
} catch (err) {
|
|
145
204
|
setError(
|
|
146
205
|
err instanceof Error ? err.message : 'Fehler beim Erstellen',
|
|
@@ -191,6 +250,7 @@ export function SettingsTab() {
|
|
|
191
250
|
setActiveSubTab(tab.key);
|
|
192
251
|
setEditing(null);
|
|
193
252
|
setShowAddForm(false);
|
|
253
|
+
setSettingsSaved(false);
|
|
194
254
|
}}
|
|
195
255
|
>
|
|
196
256
|
{tab.label}
|
|
@@ -204,136 +264,189 @@ export function SettingsTab() {
|
|
|
204
264
|
|
|
205
265
|
{error && <div style={styles.error}>{error}</div>}
|
|
206
266
|
|
|
207
|
-
{
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
<div style={styles.
|
|
211
|
-
<div style={styles.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
{editing?.id === prompt.id ? (
|
|
227
|
-
<div style={styles.editArea}>
|
|
228
|
-
<textarea
|
|
229
|
-
style={styles.textarea}
|
|
230
|
-
value={editing.value}
|
|
231
|
-
onChange={(e) =>
|
|
232
|
-
setEditing({ ...editing, value: e.target.value })
|
|
233
|
-
}
|
|
234
|
-
/>
|
|
235
|
-
<div style={styles.editButtons}>
|
|
236
|
-
<button
|
|
237
|
-
type="button"
|
|
238
|
-
style={styles.saveButton}
|
|
239
|
-
onClick={handleSave}
|
|
240
|
-
disabled={saving}
|
|
241
|
-
>
|
|
242
|
-
{saving ? 'Speichere...' : 'Speichern'}
|
|
243
|
-
</button>
|
|
244
|
-
<button
|
|
245
|
-
type="button"
|
|
246
|
-
style={styles.cancelButton}
|
|
247
|
-
onClick={handleCancel}
|
|
248
|
-
>
|
|
249
|
-
Abbrechen
|
|
250
|
-
</button>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
267
|
+
{/* Settings tabs: Homepage, Website — key-value form */}
|
|
268
|
+
{!loading && isSettingsTab(activeSubTab) && (
|
|
269
|
+
<div style={styles.addForm}>
|
|
270
|
+
<div style={styles.addFormTitle}>{guide.title}</div>
|
|
271
|
+
<div style={{ ...styles.emptyGuideText, marginBottom: '1rem' }}>{guide.description}</div>
|
|
272
|
+
{(SETTINGS_FIELDS[activeSubTab] ?? []).map((field) => (
|
|
273
|
+
<div key={field.key} style={styles.addFormField}>
|
|
274
|
+
<label style={styles.addFormLabel}>{field.label}</label>
|
|
275
|
+
{field.type === 'textarea' ? (
|
|
276
|
+
<textarea
|
|
277
|
+
style={styles.textarea}
|
|
278
|
+
value={settingsForm[field.key] ?? ''}
|
|
279
|
+
onChange={(e) =>
|
|
280
|
+
setSettingsForm({ ...settingsForm, [field.key]: e.target.value })
|
|
281
|
+
}
|
|
282
|
+
/>
|
|
253
283
|
) : (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
onClick={() => handleEdit(prompt)}
|
|
263
|
-
>
|
|
264
|
-
Bearbeiten
|
|
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>
|
|
274
|
-
</div>
|
|
275
|
-
</>
|
|
284
|
+
<input
|
|
285
|
+
style={styles.addFormInput}
|
|
286
|
+
type="text"
|
|
287
|
+
value={settingsForm[field.key] ?? ''}
|
|
288
|
+
onChange={(e) =>
|
|
289
|
+
setSettingsForm({ ...settingsForm, [field.key]: e.target.value })
|
|
290
|
+
}
|
|
291
|
+
/>
|
|
276
292
|
)}
|
|
277
293
|
</div>
|
|
278
294
|
))}
|
|
279
|
-
</div>
|
|
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
295
|
<div style={styles.editButtons}>
|
|
305
296
|
<button
|
|
306
297
|
type="button"
|
|
307
298
|
style={styles.addButton}
|
|
308
|
-
onClick={
|
|
309
|
-
disabled={saving
|
|
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
|
-
}}
|
|
299
|
+
onClick={handleSettingsSave}
|
|
300
|
+
disabled={saving}
|
|
320
301
|
>
|
|
321
|
-
|
|
302
|
+
{saving ? 'Speichere...' : 'Einstellungen speichern'}
|
|
322
303
|
</button>
|
|
304
|
+
{settingsSaved && (
|
|
305
|
+
<span style={{ color: '#4ade80', fontSize: '0.85rem', alignSelf: 'center' }}>
|
|
306
|
+
Gespeichert
|
|
307
|
+
</span>
|
|
308
|
+
)}
|
|
323
309
|
</div>
|
|
324
310
|
</div>
|
|
325
311
|
)}
|
|
326
312
|
|
|
327
|
-
{
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
style={styles.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
313
|
+
{/* Prompt tabs: Content-KI, Analyse-KI, Bild-KI — prompt cards */}
|
|
314
|
+
{!loading && !isSettingsTab(activeSubTab) && (
|
|
315
|
+
<>
|
|
316
|
+
{filteredPrompts.length === 0 && !showAddForm && (
|
|
317
|
+
<div style={styles.emptyGuide}>
|
|
318
|
+
<div style={styles.emptyGuideTitle}>{guide.title}</div>
|
|
319
|
+
<div style={styles.emptyGuideText}>{guide.description}</div>
|
|
320
|
+
{guide.example && (
|
|
321
|
+
<div style={styles.emptyGuideExample}>{guide.example}</div>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
)}
|
|
325
|
+
|
|
326
|
+
{filteredPrompts.length > 0 && (
|
|
327
|
+
<div style={styles.cardGrid}>
|
|
328
|
+
{filteredPrompts.map((prompt) => (
|
|
329
|
+
<div key={prompt.id} style={styles.settingsCard}>
|
|
330
|
+
<div style={styles.settingsCardHeader}>
|
|
331
|
+
<div style={styles.settingsItemLabel}>{prompt.name}</div>
|
|
332
|
+
<div style={styles.settingsItemCategory}>
|
|
333
|
+
{prompt.category}
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
{editing?.id === prompt.id ? (
|
|
338
|
+
<div style={styles.editArea}>
|
|
339
|
+
<textarea
|
|
340
|
+
style={styles.textarea}
|
|
341
|
+
value={editing.value}
|
|
342
|
+
onChange={(e) =>
|
|
343
|
+
setEditing({ ...editing, value: e.target.value })
|
|
344
|
+
}
|
|
345
|
+
/>
|
|
346
|
+
<div style={styles.editButtons}>
|
|
347
|
+
<button
|
|
348
|
+
type="button"
|
|
349
|
+
style={styles.saveButton}
|
|
350
|
+
onClick={handleSave}
|
|
351
|
+
disabled={saving}
|
|
352
|
+
>
|
|
353
|
+
{saving ? 'Speichere...' : 'Speichern'}
|
|
354
|
+
</button>
|
|
355
|
+
<button
|
|
356
|
+
type="button"
|
|
357
|
+
style={styles.cancelButton}
|
|
358
|
+
onClick={handleCancel}
|
|
359
|
+
>
|
|
360
|
+
Abbrechen
|
|
361
|
+
</button>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
) : (
|
|
365
|
+
<>
|
|
366
|
+
<div style={styles.settingsItemValue}>
|
|
367
|
+
{prompt.promptText}
|
|
368
|
+
</div>
|
|
369
|
+
<div style={{ padding: '0 1rem 1rem', display: 'flex', gap: '0.5rem' }}>
|
|
370
|
+
<button
|
|
371
|
+
type="button"
|
|
372
|
+
style={styles.editButton}
|
|
373
|
+
onClick={() => handleEdit(prompt)}
|
|
374
|
+
>
|
|
375
|
+
Bearbeiten
|
|
376
|
+
</button>
|
|
377
|
+
<button
|
|
378
|
+
type="button"
|
|
379
|
+
style={styles.deleteButton}
|
|
380
|
+
onClick={() => handleDelete(prompt.id)}
|
|
381
|
+
disabled={deleting === prompt.id}
|
|
382
|
+
>
|
|
383
|
+
{deleting === prompt.id ? 'Loesche...' : 'Loeschen'}
|
|
384
|
+
</button>
|
|
385
|
+
</div>
|
|
386
|
+
</>
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
389
|
+
))}
|
|
390
|
+
</div>
|
|
391
|
+
)}
|
|
392
|
+
|
|
393
|
+
{showAddForm && (
|
|
394
|
+
<div style={styles.addForm}>
|
|
395
|
+
<div style={styles.addFormTitle}>Neuen Prompt hinzufuegen ({guide.title})</div>
|
|
396
|
+
<div style={styles.addFormField}>
|
|
397
|
+
<label style={styles.addFormLabel}>Name</label>
|
|
398
|
+
<input
|
|
399
|
+
style={styles.addFormInput}
|
|
400
|
+
type="text"
|
|
401
|
+
placeholder="z.B. Blog-Artikel Prompt, SEO-Analyse, Hero-Text"
|
|
402
|
+
value={addForm.name}
|
|
403
|
+
onChange={(e) => setAddForm({ ...addForm, name: e.target.value })}
|
|
404
|
+
/>
|
|
405
|
+
</div>
|
|
406
|
+
<div style={styles.addFormField}>
|
|
407
|
+
<label style={styles.addFormLabel}>Prompt-Text</label>
|
|
408
|
+
<textarea
|
|
409
|
+
style={styles.textarea}
|
|
410
|
+
placeholder={guide.example}
|
|
411
|
+
value={addForm.promptText}
|
|
412
|
+
onChange={(e) => setAddForm({ ...addForm, promptText: e.target.value })}
|
|
413
|
+
/>
|
|
414
|
+
</div>
|
|
415
|
+
<div style={styles.editButtons}>
|
|
416
|
+
<button
|
|
417
|
+
type="button"
|
|
418
|
+
style={styles.addButton}
|
|
419
|
+
onClick={handleAdd}
|
|
420
|
+
disabled={saving || !addForm.name.trim() || !addForm.promptText.trim()}
|
|
421
|
+
>
|
|
422
|
+
{saving ? 'Erstelle...' : 'Erstellen'}
|
|
423
|
+
</button>
|
|
424
|
+
<button
|
|
425
|
+
type="button"
|
|
426
|
+
style={styles.cancelButton}
|
|
427
|
+
onClick={() => {
|
|
428
|
+
setShowAddForm(false);
|
|
429
|
+
setAddForm({ name: '', promptText: '' });
|
|
430
|
+
}}
|
|
431
|
+
>
|
|
432
|
+
Abbrechen
|
|
433
|
+
</button>
|
|
434
|
+
</div>
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
437
|
+
|
|
438
|
+
{!showAddForm && (
|
|
439
|
+
<div style={{ textAlign: 'center' as const, padding: '1rem 0' }}>
|
|
440
|
+
<button
|
|
441
|
+
type="button"
|
|
442
|
+
style={styles.addButton}
|
|
443
|
+
onClick={() => setShowAddForm(true)}
|
|
444
|
+
>
|
|
445
|
+
+ Neuen Prompt hinzufuegen
|
|
446
|
+
</button>
|
|
447
|
+
</div>
|
|
448
|
+
)}
|
|
449
|
+
</>
|
|
337
450
|
)}
|
|
338
451
|
</div>
|
|
339
452
|
);
|
package/task.md
CHANGED
|
@@ -13,7 +13,80 @@ Line 33-34: API `/api/prompts` returns `{ prompts, settings }` but the component
|
|
|
13
13
|
+ setPrompts(Array.isArray(data.prompts) ? data.prompts : []);
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
## 2.
|
|
16
|
+
## 2. Separate Settings from Prompts in SettingsTab
|
|
17
|
+
|
|
18
|
+
**File:** `src/components/editor/SettingsTab.tsx`
|
|
19
|
+
|
|
20
|
+
The SettingsTab currently treats all 5 sub-tabs identically, showing the "Neuen Prompt hinzufuegen" (prompt CRUD) UI for every tab. But **Homepage** and **Website** are **site settings** (key-value pairs in `SiteSettings` table), not AI prompts. Only **Content-KI**, **Analyse-KI**, and **Bild-KI** should show the prompt CRUD interface.
|
|
21
|
+
|
|
22
|
+
### Changes required:
|
|
23
|
+
|
|
24
|
+
1. **Import `Setting` type** from `./types` (already defined there)
|
|
25
|
+
|
|
26
|
+
2. **Add constants** at the top of the file:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const SETTINGS_TABS: SettingsSubTab[] = ['homepage', 'website'];
|
|
30
|
+
|
|
31
|
+
const SETTINGS_FIELDS: Record<string, { key: string; label: string; type: 'input' | 'textarea' }[]> = {
|
|
32
|
+
homepage: [
|
|
33
|
+
{ key: 'hero_headline', label: 'Hero Ueberschrift', type: 'input' },
|
|
34
|
+
{ key: 'hero_text', label: 'Hero Text', type: 'textarea' },
|
|
35
|
+
{ key: 'target_audience', label: 'Zielgruppe', type: 'input' },
|
|
36
|
+
{ key: 'tone', label: 'Tonalitaet', type: 'input' },
|
|
37
|
+
{ key: 'core_message', label: 'Kernbotschaft', type: 'textarea' },
|
|
38
|
+
],
|
|
39
|
+
website: [
|
|
40
|
+
{ key: 'cta_url', label: 'CTA Link', type: 'input' },
|
|
41
|
+
{ key: 'cta_style', label: 'CTA Stil', type: 'input' },
|
|
42
|
+
{ key: 'cta_prompt', label: 'CTA Prompt', type: 'textarea' },
|
|
43
|
+
{ key: 'core_tags', label: 'Core Tags (kommagetrennt)', type: 'input' },
|
|
44
|
+
{ key: 'brand_guidelines', label: 'Markenrichtlinien', type: 'textarea' },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. **Add helper function:**
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
function isSettingsTab(tab: SettingsSubTab): boolean {
|
|
53
|
+
return SETTINGS_TABS.includes(tab);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
4. **Add new state** in the component:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const [settings, setSettings] = useState<Setting[]>([]);
|
|
61
|
+
const [settingsForm, setSettingsForm] = useState<Record<string, string>>({});
|
|
62
|
+
const [settingsSaved, setSettingsSaved] = useState(false);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
5. **Update `loadPrompts`** to also load settings:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const loadedSettings: Setting[] = Array.isArray(data.settings) ? data.settings : [];
|
|
69
|
+
setSettings(loadedSettings);
|
|
70
|
+
const formValues: Record<string, string> = {};
|
|
71
|
+
for (const s of loadedSettings) {
|
|
72
|
+
formValues[s.key] = s.value;
|
|
73
|
+
}
|
|
74
|
+
setSettingsForm(formValues);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
6. **Add `handleSettingsSave`** function that POSTs each field with `{ type: 'setting', key, value }` to `/api/prompts`
|
|
78
|
+
|
|
79
|
+
7. **Update the render/return** to branch on `isSettingsTab(activeSubTab)`:
|
|
80
|
+
- **Settings tabs** (homepage, website): render a key-value form with text inputs/textareas for each field defined in `SETTINGS_FIELDS`, plus a "Einstellungen speichern" button
|
|
81
|
+
- **Prompt tabs** (content-ai, analysis-ai, image-ai): keep existing prompt card grid + "Neuen Prompt hinzufuegen" button (unchanged)
|
|
82
|
+
|
|
83
|
+
8. **Update `CATEGORY_GUIDES`** for homepage and website — change descriptions to reflect settings (not prompts), remove example text
|
|
84
|
+
|
|
85
|
+
### API usage for settings
|
|
86
|
+
- Read: from `data.settings` array returned by `GET /api/prompts`, find by `key`
|
|
87
|
+
- Write: `POST /api/prompts` with `{ type: 'setting', key, value }`
|
|
88
|
+
|
|
89
|
+
## 3. Auto-register dependencies in index.ts
|
|
17
90
|
|
|
18
91
|
**File:** `src/index.ts`
|
|
19
92
|
|
|
@@ -45,13 +118,13 @@ The consumer currently must manually add `react()`, `db()`, `output: 'server'`,
|
|
|
45
118
|
}
|
|
46
119
|
```
|
|
47
120
|
|
|
48
|
-
##
|
|
121
|
+
## 4. Bump version to 1.0.5
|
|
49
122
|
|
|
50
123
|
**File:** `package.json`
|
|
51
124
|
|
|
52
125
|
Change `"version": "1.0.4"` to `"version": "1.0.5"`.
|
|
53
126
|
|
|
54
|
-
##
|
|
127
|
+
## 5. Update README with simplified setup
|
|
55
128
|
|
|
56
129
|
**File:** `README.md`
|
|
57
130
|
|
|
@@ -74,4 +147,8 @@ Note that `prerender: false` is already present on all 18 `injectRoute()` calls
|
|
|
74
147
|
1. In a test project, use only `integrations: [ncaAiCms()]` in astro config
|
|
75
148
|
2. Run `npx astro dev`
|
|
76
149
|
3. Open `/login` — should render without errors
|
|
77
|
-
4. Log in, open `/editor`
|
|
150
|
+
4. Log in, open `/editor` → Settings tab → Homepage: should show key-value form fields, no "Neuen Prompt hinzufuegen"
|
|
151
|
+
5. Homepage tab → edit hero text → save → reload → value persists
|
|
152
|
+
6. Website tab → edit CTA URL, core tags → save → reload → values persist
|
|
153
|
+
7. Content-KI tab → still shows prompt cards with create/edit/delete
|
|
154
|
+
8. Analyse-KI and Bild-KI → same prompt UI as before
|
package/update.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# v1.0.6
|
|
2
2
|
|
|
3
|
-
##
|
|
4
|
-
-
|
|
5
|
-
-
|
|
3
|
+
## Separate settings from prompts in SettingsTab
|
|
4
|
+
- Homepage and Website tabs now show key-value settings forms (hero text, zielgruppe, CTA, core tags, etc.)
|
|
5
|
+
- Content-KI, Analyse-KI, and Bild-KI tabs show prompt card UI with create/edit/delete
|
|
6
|
+
- Settings are saved via `POST /api/prompts` with `type: setting`
|
|
7
|
+
- Each settings tab has defined fields: homepage (hero, zielgruppe, ton, kernbotschaft), website (CTA, tags, markenrichtlinien)
|
|
8
|
+
|
|
9
|
+
## Category guides and custom prompts
|
|
10
|
+
- Empty prompt categories show content marketing guidance with concrete examples
|
|
11
|
+
- New "+ Neuen Prompt hinzufuegen" button to create custom prompts
|
|
6
12
|
- Each prompt card now has a delete button
|
|
7
13
|
- API: added POST with `action: create` and DELETE endpoint for prompts
|
|
8
14
|
- PromptService: added `createPrompt()` and `deletePrompt()` methods
|