- PromptEditModal: all inputs/textareas now full-width - Labels positioned above fields (not inline) - Text left-aligned (was right-aligned) - Added resize:vertical for textareas - Side-by-side comparison with word-wrap - Follows app-wide form design pattern Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
191 lines
6.5 KiB
JavaScript
191 lines
6.5 KiB
JavaScript
import { useState } from 'react'
|
||
import { api } from '../utils/api'
|
||
|
||
export default function PromptGenerator({ onGenerated, onClose }) {
|
||
const [goal, setGoal] = useState('')
|
||
const [dataCategories, setDataCategories] = useState(['körper', 'ernährung'])
|
||
const [exampleOutput, setExampleOutput] = useState('')
|
||
const [exampleData, setExampleData] = useState(null)
|
||
const [generating, setGenerating] = useState(false)
|
||
const [loadingExample, setLoadingExample] = useState(false)
|
||
|
||
const categories = [
|
||
{ id: 'körper', label: 'Körper (Gewicht, KF, Umfänge)' },
|
||
{ id: 'ernährung', label: 'Ernährung (Kalorien, Makros)' },
|
||
{ id: 'training', label: 'Training (Volumen, Typen)' },
|
||
{ id: 'schlaf', label: 'Schlaf (Dauer, Qualität)' },
|
||
{ id: 'vitalwerte', label: 'Vitalwerte (RHR, HRV, VO2max)' },
|
||
{ id: 'ziele', label: 'Ziele (Fortschritt, Prognose)' }
|
||
]
|
||
|
||
const handleToggleCategory = (catId) => {
|
||
if (dataCategories.includes(catId)) {
|
||
setDataCategories(dataCategories.filter(c => c !== catId))
|
||
} else {
|
||
setDataCategories([...dataCategories, catId])
|
||
}
|
||
}
|
||
|
||
const handleShowExampleData = async () => {
|
||
try {
|
||
setLoadingExample(true)
|
||
const placeholders = await api.listPlaceholders()
|
||
setExampleData(placeholders)
|
||
} catch (e) {
|
||
alert('Fehler: ' + e.message)
|
||
} finally {
|
||
setLoadingExample(false)
|
||
}
|
||
}
|
||
|
||
const handleGenerate = async () => {
|
||
if (!goal.trim()) {
|
||
alert('Bitte Ziel beschreiben')
|
||
return
|
||
}
|
||
if (dataCategories.length === 0) {
|
||
alert('Bitte mindestens einen Datenbereich wählen')
|
||
return
|
||
}
|
||
|
||
try {
|
||
setGenerating(true)
|
||
const result = await api.generatePrompt({
|
||
goal,
|
||
data_categories: dataCategories,
|
||
example_output: exampleOutput || null
|
||
})
|
||
onGenerated(result)
|
||
} catch (e) {
|
||
alert('Fehler beim Generieren: ' + e.message)
|
||
} finally {
|
||
setGenerating(false)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div style={{
|
||
position:'fixed', inset:0, background:'rgba(0,0,0,0.6)',
|
||
display:'flex', alignItems:'center', justifyContent:'center',
|
||
zIndex:1001, padding:20
|
||
}}>
|
||
<div style={{
|
||
background:'var(--bg)', borderRadius:12, maxWidth:700, width:'100%',
|
||
maxHeight:'90vh', overflow:'auto', padding:24
|
||
}}>
|
||
<h2 style={{margin:'0 0 24px 0', fontSize:18, fontWeight:600}}>
|
||
🤖 KI-Prompt generieren
|
||
</h2>
|
||
|
||
{/* Step 1: Goal */}
|
||
<div style={{marginBottom:24}}>
|
||
<label className="form-label" style={{display:'block', marginBottom:6}}>
|
||
1️⃣ Was möchtest du analysieren?
|
||
</label>
|
||
<textarea
|
||
className="form-input"
|
||
value={goal}
|
||
onChange={e => setGoal(e.target.value)}
|
||
rows={3}
|
||
placeholder="Beispiel: Ich möchte wissen ob meine Proteinzufuhr ausreichend ist für Muskelaufbau und wie ich sie optimieren kann."
|
||
style={{width:'100%', textAlign:'left', resize:'vertical'}}
|
||
/>
|
||
</div>
|
||
|
||
{/* Step 2: Data Categories */}
|
||
<div style={{marginBottom:24}}>
|
||
<label className="form-label">
|
||
2️⃣ Welche Daten sollen analysiert werden?
|
||
</label>
|
||
<div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:8}}>
|
||
{categories.map(cat => (
|
||
<label
|
||
key={cat.id}
|
||
style={{
|
||
display:'flex', alignItems:'center', gap:8,
|
||
padding:8, background:'var(--surface)', borderRadius:6,
|
||
cursor:'pointer', fontSize:13
|
||
}}
|
||
>
|
||
<input
|
||
type="checkbox"
|
||
checked={dataCategories.includes(cat.id)}
|
||
onChange={() => handleToggleCategory(cat.id)}
|
||
/>
|
||
{cat.label}
|
||
</label>
|
||
))}
|
||
</div>
|
||
|
||
<button
|
||
onClick={handleShowExampleData}
|
||
disabled={loadingExample}
|
||
style={{
|
||
marginTop:12, fontSize:12, padding:'6px 12px',
|
||
background:'var(--surface2)', border:'1px solid var(--border)',
|
||
borderRadius:6, cursor:'pointer'
|
||
}}
|
||
>
|
||
{loadingExample ? 'Lädt...' : '📊 Beispieldaten anzeigen'}
|
||
</button>
|
||
</div>
|
||
|
||
{/* Example Data */}
|
||
{exampleData && (
|
||
<div style={{
|
||
marginBottom:24, padding:12, background:'var(--surface2)',
|
||
borderRadius:8, fontSize:11, fontFamily:'monospace',
|
||
maxHeight:200, overflow:'auto'
|
||
}}>
|
||
<strong style={{fontFamily:'var(--font)'}}>Deine aktuellen Daten:</strong>
|
||
<pre style={{marginTop:8, whiteSpace:'pre-wrap'}}>
|
||
{JSON.stringify(exampleData, null, 2)}
|
||
</pre>
|
||
</div>
|
||
)}
|
||
|
||
{/* Step 3: Desired Format */}
|
||
<div style={{marginBottom:24}}>
|
||
<label className="form-label" style={{display:'block', marginBottom:6}}>
|
||
3️⃣ Gewünschtes Antwort-Format (optional)
|
||
</label>
|
||
<textarea
|
||
className="form-input"
|
||
value={exampleOutput}
|
||
onChange={e => setExampleOutput(e.target.value)}
|
||
rows={4}
|
||
placeholder={'Beispiel:\n## Analyse\n[Bewertung]\n\n## Empfehlungen\n- Punkt 1\n- Punkt 2'}
|
||
style={{width:'100%', textAlign:'left', fontFamily:'monospace', fontSize:12, resize:'vertical'}}
|
||
/>
|
||
</div>
|
||
|
||
{/* Info Box */}
|
||
<div style={{
|
||
marginBottom:24, padding:12, background:'var(--surface)',
|
||
border:'1px solid var(--border)', borderRadius:8, fontSize:12,
|
||
color:'var(--text3)'
|
||
}}>
|
||
<strong style={{color:'var(--text2)'}}>💡 Tipp:</strong> Je präziser deine Zielbeschreibung,
|
||
desto besser der generierte Prompt. Die KI wählt automatisch passende Platzhalter
|
||
und strukturiert die Analyse optimal.
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div style={{display:'flex', gap:12}}>
|
||
<button
|
||
className="btn btn-primary"
|
||
onClick={handleGenerate}
|
||
disabled={generating || !goal.trim() || dataCategories.length === 0}
|
||
style={{flex:1}}
|
||
>
|
||
{generating ? '⏳ Generiere...' : '🚀 Prompt generieren'}
|
||
</button>
|
||
<button className="btn" onClick={onClose}>
|
||
Abbrechen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|