feat: expert mode + stage outputs in value table (Issue #47)
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s

FEATURE: Experten-Modus 🔬
- Toggle-Button in Wertetabelle
- Normal: Nur gefüllte Werte anzeigen
- Experten: Alle Platzhalter inkl. leere/technische
- Anzeige: "(+X ausgeblendet)" wenn Werte gefiltert
- Button-Style: Accent wenn aktiv

FILTER: Leere Werte ausblenden (Normal-Modus)
- Filtert: '', 'nicht verfügbar', '[Nicht verfügbar]'
- Zeigt nur relevante Nutzer-Daten
- Experten-Modus zeigt alles

FEATURE: Stage-Outputs in Wertetabelle 
ROOT CAUSE: stage_N_key Platzhalter hatten keine Werte
- Stage-Outputs (z.B. stage_1_body) sind Basis-Analysen-Ergebnisse
- Wurden nicht in cleaned_values gefunden (nur statische Platzhalter)
FIX:
- Collect stage outputs aus result.debug.stages[].output
- Store als stage_N_key dict
- Lookup: erst stage_outputs, dann cleaned_values
- Description: "Output aus Stage X (Basis-Analyse)"
- JSON-Werte automatisch serialisiert

BEISPIEL Pipeline-Wertetabelle:
┌──────────────────────────────────────────────┐
│ 📊 Verwendete Werte (8) (+3 ausgeblendet) 🔬│
│ ┌──────────────────────────────────────────┐ │
│ │ weight_aktuell  │ 85.2 kg   │ Gewicht  │ │
│ │ stage_1_body    │ {"bmi":...│ Output...│ │ ← Stage output!
│ │ stage_1_nutr... │ {"kcal"...│ Output...│ │
│ └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘

AKTIVIERUNG Experten-Modus:
1. Analyse öffnen
2. "📊 Verwendete Werte" aufklappen
3. Button "🔬 Experten-Modus" klicken
4. Zeigt alle Platzhalter (auch leere stage outputs)

version: 9.8.0 (feature)
module: prompts 2.3.0, insights 1.6.0
This commit is contained in:
Lars 2026-03-26 12:44:28 +01:00
parent 15bd6cddeb
commit e799edbae4
2 changed files with 82 additions and 26 deletions

View File

@ -856,6 +856,19 @@ async def execute_unified_prompt(
elif result['type'] == 'pipeline':
# Pipeline: collect from all stages
stages_debug = result['debug'].get('stages', [])
# First, collect stage outputs (outputs from base prompts in each stage)
stage_outputs = {}
for stage_debug in stages_debug:
stage_num = stage_debug.get('stage', 0)
stage_output = stage_debug.get('output', {})
if isinstance(stage_output, dict):
for output_key, output_value in stage_output.items():
# Store stage outputs (e.g., stage_1_body)
placeholder_key = f"stage_{stage_num}_{output_key}"
stage_outputs[placeholder_key] = output_value
# Collect all resolved placeholders from prompts
for stage_debug in stages_debug:
for prompt_debug in stage_debug.get('prompts', []):
resolved_keys = []
@ -867,20 +880,25 @@ async def execute_unified_prompt(
for key in resolved_keys:
if key not in metadata['placeholders']: # Avoid duplicates
# Get full untruncated value
value = cleaned_values.get(key, '')
# Get value: first try stage outputs, then cleaned_values
value = stage_outputs.get(key, cleaned_values.get(key, ''))
# Find description in catalog
desc = None
for cat_items in catalog.values():
matching = [item for item in cat_items if item['key'] == key]
if matching:
desc = matching[0].get('description', '')
break
# For stage output placeholders, add special description
if key.startswith('stage_'):
desc = f"Output aus Stage {key.split('_')[1]} (Basis-Analyse)"
else:
# Find description in catalog
desc = None
for cat_items in catalog.values():
matching = [item for item in cat_items if item['key'] == key]
if matching:
desc = matching[0].get('description', '')
break
desc = desc or ''
metadata['placeholders'][key] = {
'value': value,
'description': desc or ''
'value': value if isinstance(value, str) else json.dumps(value, ensure_ascii=False),
'description': desc
}
# Save to database with metadata

View File

@ -24,6 +24,7 @@ function InsightCard({ ins, onDelete, defaultOpen=false, prompts=[] }) {
const showOnlyValues = isBasePrompt && isJsonOutput && Object.keys(placeholdersRaw).length > 0
const [showValues, setShowValues] = useState(showOnlyValues) // Auto-expand for base prompts with JSON
const [expertMode, setExpertMode] = useState(false) // Show empty/technical placeholders
// Find matching prompt to get display_name
const prompt = prompts.find(p => p.slug === ins.scope)
@ -31,8 +32,20 @@ function InsightCard({ ins, onDelete, defaultOpen=false, prompts=[] }) {
// Use already-parsed metadata
const metadata = metadataRaw
const placeholders = placeholdersRaw
const allPlaceholders = placeholdersRaw
// Filter placeholders: In normal mode, hide empty values
const placeholders = expertMode
? allPlaceholders
: Object.fromEntries(
Object.entries(allPlaceholders).filter(([key, data]) => {
const val = data.value || ''
return val.trim() !== '' && val !== 'nicht verfügbar' && val !== '[Nicht verfügbar]'
})
)
const placeholderCount = Object.keys(placeholders).length
const hiddenCount = Object.keys(allPlaceholders).length - placeholderCount
return (
<div className="card section-gap" style={{borderLeft:`3px solid var(--accent)`}}>
@ -83,20 +96,45 @@ function InsightCard({ ins, onDelete, defaultOpen=false, prompts=[] }) {
{/* Value Table */}
{placeholderCount > 0 && (
<div style={{ marginTop: 16, borderTop: '1px solid var(--border)', paddingTop: 12 }}>
<div
onClick={() => setShowValues(!showValues)}
style={{
cursor: 'pointer',
fontSize: 12,
color: 'var(--text2)',
fontWeight: 600,
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
{showValues ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
📊 Verwendete Werte ({placeholderCount})
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div
onClick={() => setShowValues(!showValues)}
style={{
cursor: 'pointer',
fontSize: 12,
color: 'var(--text2)',
fontWeight: 600,
display: 'flex',
alignItems: 'center',
gap: 6
}}
>
{showValues ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
📊 Verwendete Werte ({placeholderCount})
{hiddenCount > 0 && !expertMode && (
<span style={{ fontSize: 10, color: 'var(--text3)', fontWeight: 400 }}>
(+{hiddenCount} ausgeblendet)
</span>
)}
</div>
{showValues && Object.keys(allPlaceholders).length > 0 && (
<button
onClick={(e) => {
e.stopPropagation()
setExpertMode(!expertMode)
}}
className="btn"
style={{
fontSize: 10,
padding: '4px 8px',
background: expertMode ? 'var(--accent)' : 'var(--surface)',
color: expertMode ? 'white' : 'var(--text2)'
}}
>
🔬 Experten-Modus
</button>
)}
</div>
{showValues && (