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>
|
||||
) : (
|
||||
<textarea
|
||||
className="form-input"
|
||||
value={p.template || ''}
|
||||
onChange={e => updateStagePrompt(stage.stage, pIdx, 'template', e.target.value)}
|
||||
rows={3}
|
||||
placeholder="Inline-Template mit {{placeholders}}..."
|
||||
style={{ width: '100%', fontSize: 12, textAlign: 'left', resize: 'vertical', fontFamily: 'monospace' }}
|
||||
/>
|
||||
<div>
|
||||
<textarea
|
||||
className="form-input"
|
||||
value={p.template || ''}
|
||||
onChange={e => updateStagePrompt(stage.stage, pIdx, 'template', e.target.value)}
|
||||
rows={3}
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { api } from '../utils/api'
|
||||
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)
|
||||
|
|
@ -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 () => {
|
||||
setEditingPrompt(null)
|
||||
setShowNewPrompt(false)
|
||||
|
|
@ -388,6 +431,21 @@ export default function AdminPromptsPage() {
|
|||
>
|
||||
<Edit size={16} color="var(--accent)" />
|
||||
</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
|
||||
onClick={() => handleDuplicate(prompt)}
|
||||
style={{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user