feat: placeholder chips + convert to base prompt (Issue #28)
New features: 1. Placeholder chips now visible in pipeline inline templates - Click to insert: weight_data, nutrition_data, activity_data, etc. - Same UX as base prompts 2. Convert to Base Prompt button - New icon (ArrowDownToLine) in actions column - Only visible for 1-stage pipeline prompts - Converts pipeline → base by extracting inline template - Validates: must be 1-stage, 1-prompt, inline source This allows migrated prompts to be properly categorized as base prompts for reuse in other pipelines.
This commit is contained in:
parent
7dda520c9b
commit
b058b0fd6f
|
|
@ -526,14 +526,41 @@ export default function UnifiedPromptModal({ prompt, onSave, onClose }) {
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
) : (
|
) : (
|
||||||
<textarea
|
<div>
|
||||||
className="form-input"
|
<textarea
|
||||||
value={p.template || ''}
|
className="form-input"
|
||||||
onChange={e => updateStagePrompt(stage.stage, pIdx, 'template', e.target.value)}
|
value={p.template || ''}
|
||||||
rows={3}
|
onChange={e => updateStagePrompt(stage.stage, pIdx, 'template', e.target.value)}
|
||||||
placeholder="Inline-Template mit {{placeholders}}..."
|
rows={3}
|
||||||
style={{ width: '100%', fontSize: 12, textAlign: 'left', resize: 'vertical', fontFamily: 'monospace' }}
|
placeholder="Inline-Template mit {{placeholders}}..."
|
||||||
/>
|
style={{ width: '100%', fontSize: 12, textAlign: 'left', resize: 'vertical', fontFamily: 'monospace' }}
|
||||||
|
/>
|
||||||
|
<div style={{ fontSize: 10, color: 'var(--text3)', marginTop: 4 }}>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 }}>
|
||||||
|
{['weight_data', 'nutrition_data', 'activity_data', 'sleep_data', 'vitals_baseline', 'blood_pressure', 'profile_id', 'today'].map(ph => (
|
||||||
|
<code
|
||||||
|
key={ph}
|
||||||
|
onClick={() => {
|
||||||
|
const placeholder = `{{${ph}}}`
|
||||||
|
const currentValue = p.template || ''
|
||||||
|
updateStagePrompt(stage.stage, pIdx, 'template', currentValue + placeholder)
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
padding: '2px 4px',
|
||||||
|
background: 'var(--surface2)',
|
||||||
|
borderRadius: 4,
|
||||||
|
fontSize: 9,
|
||||||
|
cursor: 'pointer',
|
||||||
|
border: '1px solid var(--border)'
|
||||||
|
}}
|
||||||
|
title="Klicken zum Einfügen"
|
||||||
|
>
|
||||||
|
{'{{'}{ph}{'}}'}
|
||||||
|
</code>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { api } from '../utils/api'
|
import { api } from '../utils/api'
|
||||||
import UnifiedPromptModal from '../components/UnifiedPromptModal'
|
import UnifiedPromptModal from '../components/UnifiedPromptModal'
|
||||||
import { Star, Trash2, Edit, Copy, Filter } from 'lucide-react'
|
import { Star, Trash2, Edit, Copy, Filter, ArrowDownToLine } from 'lucide-react'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin Prompts Page - Unified System (Issue #28 Phase 3)
|
* Admin Prompts Page - Unified System (Issue #28 Phase 3)
|
||||||
|
|
@ -94,6 +94,49 @@ export default function AdminPromptsPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleConvertToBase = async (prompt) => {
|
||||||
|
// Convert a 1-stage pipeline to a base prompt
|
||||||
|
if (prompt.type !== 'pipeline') {
|
||||||
|
alert('Nur Pipeline-Prompts können konvertiert werden')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stages = typeof prompt.stages === 'string'
|
||||||
|
? JSON.parse(prompt.stages)
|
||||||
|
: prompt.stages
|
||||||
|
|
||||||
|
if (!stages || stages.length !== 1) {
|
||||||
|
alert('Nur 1-stage Pipeline-Prompts können zu Basis-Prompts konvertiert werden')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stage1 = stages[0]
|
||||||
|
if (!stage1.prompts || stage1.prompts.length !== 1) {
|
||||||
|
alert('Stage muss genau einen Prompt haben')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstPrompt = stage1.prompts[0]
|
||||||
|
if (firstPrompt.source !== 'inline' || !firstPrompt.template) {
|
||||||
|
alert('Nur inline Templates können konvertiert werden')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm(`"${prompt.name}" zu Basis-Prompt konvertieren?`)) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.updateUnifiedPrompt(prompt.id, {
|
||||||
|
type: 'base',
|
||||||
|
template: firstPrompt.template,
|
||||||
|
output_format: firstPrompt.output_format || 'text',
|
||||||
|
stages: null
|
||||||
|
})
|
||||||
|
await loadPrompts()
|
||||||
|
} catch (e) {
|
||||||
|
alert('Fehler: ' + e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setEditingPrompt(null)
|
setEditingPrompt(null)
|
||||||
setShowNewPrompt(false)
|
setShowNewPrompt(false)
|
||||||
|
|
@ -388,6 +431,21 @@ export default function AdminPromptsPage() {
|
||||||
>
|
>
|
||||||
<Edit size={16} color="var(--accent)" />
|
<Edit size={16} color="var(--accent)" />
|
||||||
</button>
|
</button>
|
||||||
|
{/* Show convert button for 1-stage pipelines */}
|
||||||
|
{prompt.type === 'pipeline' && getStageCount(prompt) === 1 && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleConvertToBase(prompt)}
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: 4
|
||||||
|
}}
|
||||||
|
title="Zu Basis-Prompt konvertieren"
|
||||||
|
>
|
||||||
|
<ArrowDownToLine size={16} color="#6366f1" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDuplicate(prompt)}
|
onClick={() => handleDuplicate(prompt)}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user