mitai-jinkendo/docs/issues/issue-51-prompt-page-assignment.md
Lars cd2609da7c
All checks were successful
Deploy Development / deploy (push) Successful in 48s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s
feat: Feature Request #49 - Prompt-Zuordnung zu Verlaufsseiten
UX Enhancement: Kontextbezogene KI-Analysen

Features:
- Prompts auf Verlaufsseiten verfügbar machen
- Mehrfachauswahl: Prompt auf mehreren Seiten
- Inline-Analyse via Modal
- Wiederverwendbare PagePrompts Komponente

Technisch:
- DB: available_on JSONB column
- API: GET /api/prompts/for-page/{page_slug}
- UI: Page-Auswahl im Prompt-Editor

Aufwand: 6-8h, Priority: Medium
Gitea: Issue #49
2026-03-26 15:12:39 +01:00

14 KiB

Feature: Prompt-Zuordnung zu Verlaufsseiten

Labels: feature, ux, enhancement Priority: Medium (Phase 1-2) Related: Issue #28 (Unified Prompt System - Complete)

Beschreibung

KI-Prompts sollen flexibel auf verschiedenen Verlaufsseiten verfügbar gemacht werden können. Jeder Prompt kann auf mehreren Seiten gleichzeitig angeboten werden (Mehrfachauswahl).

Problem (aktueller Stand)

Aktuell:

  • Prompts sind nur über die zentrale Analyse-Seite (📊 Analyse) verfügbar
  • Kein kontextbezogener Zugriff auf relevante Analysen
  • User muss immer zur Analyse-Seite navigieren

Beispiel-Szenario:

User ist auf: Gewicht → Verlauf
Will: Gewichtstrend analysieren
Muss: Zur Analyse-Seite → Prompt auswählen → Zurück

Wünschenswert:

User ist auf: Gewicht → Verlauf
Sieht: "🤖 KI-Analyse" Button mit relevanten Prompts
Kann: Direkt "Gewichtstrend-Analyse" starten

Gewünschtes Verhalten

1. Prompt-Konfiguration erweitern

Admin → KI-Prompts → Prompt bearbeiten:

┌─────────────────────────────────────────────┐
│ Prompt bearbeiten: Gewichtstrend-Analyse    │
├─────────────────────────────────────────────┤
│ Name: Gewichtstrend-Analyse                 │
│ Slug: weight_trend                          │
│ Type: Pipeline                              │
│                                             │
│ 📍 Verfügbar auf Seiten:                   │
│ ┌─────────────────────────────────────────┐ │
│ │ ☑ Analyse (Hauptseite)                  │ │
│ │ ☑ Gewicht → Verlauf                     │ │
│ │ ☐ Umfänge → Verlauf                     │ │
│ │ ☐ Caliper → Verlauf                     │ │
│ │ ☐ Aktivität → Verlauf                   │ │
│ │ ☐ Ernährung → Verlauf                   │ │
│ │ ☐ Schlaf → Verlauf                      │ │
│ │ ☐ Vitalwerte → Verlauf                  │ │
│ │ ☐ Dashboard                             │ │
│ └─────────────────────────────────────────┘ │
│                                             │
│ [Speichern]  [Abbrechen]                   │
└─────────────────────────────────────────────┘

Mehrfachauswahl:

  • Ein Prompt kann auf mehreren Seiten gleichzeitig verfügbar sein
  • Mindestens eine Seite muss ausgewählt sein
  • Default: "Analyse (Hauptseite)" ist immer vorausgewählt

2. UI auf Verlaufsseiten

Gewicht → Verlauf:

┌─────────────────────────────────────────────┐
│ 📊 Gewicht - Verlauf                        │
│ [Filter: 7d] [30d] [90d] [Alle]            │
├─────────────────────────────────────────────┤
│                                             │
│ [Chart: Gewichtsverlauf]                    │
│                                             │
├─────────────────────────────────────────────┤
│ 🤖 KI-Analysen                             │
│ ┌─────────────────────────────────────────┐ │
│ │ Gewichtstrend-Analyse        [▶ Starten]│ │
│ │ Körperkomposition-Check      [▶ Starten]│ │
│ └─────────────────────────────────────────┘ │
│                                             │
│ [Einträge-Tabelle...]                       │
└─────────────────────────────────────────────┘

Features:

  • Kompaktes Widget unterhalb des Charts
  • Nur relevante Prompts werden angezeigt
  • Button startet Analyse inline (Modal oder expandierend)
  • Ergebnis wird direkt auf der Seite angezeigt

3. Inline-Analyse Anzeige

Option A: Modal (empfohlen für MVP):

Click auf [▶ Starten]
  ↓
┌─────────────────────────────────────────────┐
│ ✕ Gewichtstrend-Analyse                     │
├─────────────────────────────────────────────┤
│ [Spinner] Analysiere Gewichtsdaten...       │
│                                             │
│ [Nach Abschluss:]                           │
│ Analyse-Text...                             │
│                                             │
│ 📊 Verwendete Werte (12)  [🔬 Experten]    │
│ [Value Table...]                            │
│                                             │
│ [Schließen]  [In Verlauf speichern]        │
└─────────────────────────────────────────────┘

Option B: Expandierend (später):

Click auf [▶ Starten]
  ↓
Widget expandiert nach unten
Zeigt Analyse-Ergebnis inline
[△ Einklappen] Button

Technische Umsetzung

1. Datenbankschema erweitern

Tabelle: ai_prompts

ALTER TABLE ai_prompts ADD COLUMN available_on JSONB DEFAULT '["analysis"]';

COMMENT ON COLUMN ai_prompts.available_on IS
  'Array of page slugs where prompt is available.
   Values: analysis, weight_history, circ_history, caliper_history,
   activity_history, nutrition_history, sleep_history, vitals_history, dashboard';

-- Migration 022

Beispiel-Werte:

{
  "slug": "weight_trend",
  "name": "Gewichtstrend-Analyse",
  "available_on": ["analysis", "weight_history"]
}

{
  "slug": "pipeline_master",
  "name": "Vollständige Analyse",
  "available_on": ["analysis", "dashboard"]
}

{
  "slug": "nutrition_check",
  "name": "Ernährungs-Check",
  "available_on": ["analysis", "nutrition_history", "activity_history"]
}

2. Backend API erweitern

Neuer Endpoint: GET /api/prompts/for-page/{page_slug}

@router.get("/for-page/{page_slug}")
def get_prompts_for_page(page_slug: str, session: dict = Depends(require_auth)):
    """Get all prompts available for a specific page.

    Args:
        page_slug: Page identifier (e.g., 'weight_history', 'analysis')

    Returns:
        List of prompts with available_on containing page_slug
    """
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute(
            """SELECT id, name, slug, type, description, available_on
               FROM ai_prompts
               WHERE available_on @> %s
               ORDER BY name""",
            (json.dumps([page_slug]),)
        )
        return [r2d(row) for row in cur.fetchall()]

Beispiel-Aufruf:

// In WeightPage.jsx
const prompts = await api.getPromptsForPage('weight_history')
// Returns: [{slug: 'weight_trend', name: 'Gewichtstrend-Analyse', ...}]

Prompt CRUD erweitern:

@router.put("/unified/{id}")
def update_unified_prompt(id: str, p: UnifiedPromptCreate, session=Depends(require_admin)):
    # ... existing code ...
    cur.execute(
        """UPDATE ai_prompts
           SET name=%s, slug=%s, template=%s, ..., available_on=%s
           WHERE id=%s""",
        (..., json.dumps(p.available_on), id)
    )

3. Frontend: Prompt-Editor erweitern

UnifiedPromptModal.jsx:

const PAGE_OPTIONS = [
  { value: 'analysis', label: '📊 Analyse (Hauptseite)', default: true },
  { value: 'weight_history', label: '⚖️ Gewicht → Verlauf' },
  { value: 'circ_history', label: '📏 Umfänge → Verlauf' },
  { value: 'caliper_history', label: '📐 Caliper → Verlauf' },
  { value: 'activity_history', label: '🏃 Aktivität → Verlauf' },
  { value: 'nutrition_history', label: '🍎 Ernährung → Verlauf' },
  { value: 'sleep_history', label: '😴 Schlaf → Verlauf' },
  { value: 'vitals_history', label: '❤️ Vitalwerte → Verlauf' },
  { value: 'dashboard', label: '🏠 Dashboard' },
]

// In form:
<div className="form-row">
  <label>Verfügbar auf Seiten</label>
  <div style={{display: 'flex', flexDirection: 'column', gap: 8}}>
    {PAGE_OPTIONS.map(opt => (
      <label key={opt.value} style={{display: 'flex', gap: 8, alignItems: 'center'}}>
        <input
          type="checkbox"
          checked={availableOn.includes(opt.value)}
          onChange={e => {
            if (e.target.checked) {
              setAvailableOn([...availableOn, opt.value])
            } else {
              // Don't allow unchecking all
              if (availableOn.length > 1) {
                setAvailableOn(availableOn.filter(v => v !== opt.value))
              }
            }
          }}
        />
        {opt.label}
      </label>
    ))}
  </div>
</div>

4. Frontend: Verlaufsseiten erweitern

WeightPage.jsx (Beispiel):

function WeightPage() {
  const [prompts, setPrompts] = useState([])
  const [runningAnalysis, setRunningAnalysis] = useState(null)
  const [analysisResult, setAnalysisResult] = useState(null)

  useEffect(() => {
    loadPrompts()
  }, [])

  const loadPrompts = async () => {
    try {
      const data = await api.getPromptsForPage('weight_history')
      setPrompts(data)
    } catch(e) {
      console.error('Failed to load prompts:', e)
    }
  }

  const runAnalysis = async (promptSlug) => {
    setRunningAnalysis(promptSlug)
    try {
      const result = await api.executePrompt(promptSlug, {save: true})
      setAnalysisResult(result)
    } catch(e) {
      setError(e.message)
    } finally {
      setRunningAnalysis(null)
    }
  }

  return (
    <div className="page">
      <h1>Gewicht - Verlauf</h1>

      {/* Chart */}
      <WeightChart data={data} />

      {/* AI Prompts Widget */}
      {prompts.length > 0 && (
        <div className="ai-prompts-widget">
          <h3>🤖 KI-Analysen</h3>
          {prompts.map(p => (
            <button
              key={p.slug}
              onClick={() => runAnalysis(p.slug)}
              disabled={runningAnalysis === p.slug}
            >
              {p.name} {runningAnalysis === p.slug ? '⏳' : '▶'}
            </button>
          ))}
        </div>
      )}

      {/* Analysis Result Modal */}
      {analysisResult && (
        <AnalysisResultModal
          result={analysisResult}
          onClose={() => setAnalysisResult(null)}
        />
      )}

      {/* Data Table */}
      <DataTable entries={entries} />
    </div>
  )
}

Wiederverwendbare Komponente:

// components/PagePrompts.jsx
export function PagePrompts({ pageSlug }) {
  // ... logic ...
  return (
    <div className="page-prompts">
      <h3>🤖 KI-Analysen</h3>
      {prompts.map(p => (
        <PromptButton key={p.slug} prompt={p} onRun={runAnalysis} />
      ))}
    </div>
  )
}

// Usage in any page:
<PagePrompts pageSlug="weight_history" />

Akzeptanzkriterien

  • DB-Migration: available_on JSONB column in ai_prompts
  • Backend: GET /api/prompts/for-page/{page_slug} Endpoint
  • Backend: CRUD operations unterstützen available_on
  • Frontend: Prompt-Editor zeigt Page-Auswahl (Mehrfachauswahl)
  • Frontend: Mindestens 1 Page muss ausgewählt sein
  • Frontend: Wiederverwendbare PagePrompts Komponente
  • Frontend: Integration in mind. 2 Verlaufsseiten (Weight, Nutrition)
  • UI: Inline-Analyse via Modal mit Value Table
  • UI: Loading-State während Analyse läuft
  • Dokumentation: API-Dokumentation aktualisiert

Abschätzung

Aufwand: 6-8 Stunden

  • 1h: DB-Migration + Backend Endpoint
  • 2h: Prompt-Editor erweitern (Page-Auswahl)
  • 2h: PagePrompts Komponente + Modal
  • 2h: Integration in Verlaufsseiten (2-3 Seiten)
  • 1h: Testing + Feintuning

Priorität: Medium

  • Verbessert UX erheblich (kontextbezogene Analysen)
  • Nutzt bestehendes Prompt-System (Issue #28)
  • Relativ einfach zu implementieren (kein neues Backend-System)

Use Cases

UC1: Gewichtstrend auf Gewicht-Seite

User: Navigiert zu "Gewicht → Verlauf"
System: Zeigt Gewichts-Chart + verfügbare Prompts
User: Click "Gewichtstrend-Analyse ▶"
System: Startet Analyse, zeigt Modal mit Ergebnis
User: Click "In Verlauf speichern"
System: Speichert in ai_insights, zeigt in Analyse-Verlauf

UC2: Ernährungs-Check auf Ernährung-Seite

User: Navigiert zu "Ernährung → Verlauf"
System: Zeigt Ernährungs-Charts + verfügbare Prompts
User: Click "Ernährungs-Check ▶"
System: Analysiert Makros + Kalorien der letzten 7 Tage
User: Sieht Empfehlungen direkt auf Ernährungs-Seite

UC3: Multi-Page Prompt (z.B. "Vollständige Analyse")

Admin: Konfiguriert "Vollständige Analyse"
  - Verfügbar auf: [Analyse, Dashboard, Gewicht, Ernährung]
User: Sieht denselben Prompt auf 4 verschiedenen Seiten
User: Kann von überall die gleiche umfassende Analyse starten

Notizen

  • Rückwärtskompatibilität: Bestehende Prompts ohne available_on → Default ["analysis"]
  • Migration: Alle existierenden Prompts bekommen ["analysis"] gesetzt
  • Permissions: Prompts respektieren weiterhin Feature-Enforcement (ai_calls)
  • Caching: Prompts könnten gecacht werden (selten geändert)
  • Mobile: PagePrompts sollte auch auf Mobile gut aussehen (Stack-Layout)
  • Performance: Lazy-Loading der Prompts (nur laden wenn Seite besucht)

Erweiterungen (Future)

  • Conditional Display: Prompts nur anzeigen wenn Daten vorhanden
    • Beispiel: "Gewichtstrend" nur wenn min. 3 Gewichts-Einträge
  • Quick Actions: Direkt-Buttons im Chart (ohne separates Widget)
  • Page-spezifische Variablen: Automatisch aktuelle Filter übergeben
    • Beispiel: Wenn "30d" Filter aktiv → {{timeframe}} = 30
  • Prompt-Templates pro Page: Vordefinierte Vorlagen für jede Seite
  • Favoriten: User kann Prompts auf Seiten favorisieren (User-spezifisch)

Verwandte Issues

  • #28: Unified Prompt System (Basis für dieses Feature)
  • #45: KI Prompt-Optimierer (könnte Page-Kontext nutzen)
  • #46: KI Prompt-Ersteller (sollte Page-Auswahl anbieten)