From f5c886fc13cffe328533ef7404a84b9de7b54be4 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 22 May 2026 22:30:29 +0200 Subject: [PATCH] Enhance ExercisePickerModal with Improved Planning Context Handling - Introduced `planningUnitId` and `expectPlanningSearch` props to better manage planning context for exercise suggestions. - Refactored logic to resolve planning unit ID and construct active planning context, enhancing the accuracy of exercise suggestions. - Implemented checks to block planning search when necessary, providing clearer user feedback in the UI. - Updated `TrainingUnitEditPage` to pass the correct planning unit ID, ensuring seamless integration with the exercise picker. --- .../src/components/ExercisePickerModal.jsx | 98 ++++++++++++++++--- frontend/src/pages/TrainingUnitEditPage.jsx | 5 + 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/ExercisePickerModal.jsx b/frontend/src/components/ExercisePickerModal.jsx index 0cda9dd..7a28d26 100644 --- a/frontend/src/components/ExercisePickerModal.jsx +++ b/frontend/src/components/ExercisePickerModal.jsx @@ -38,7 +38,11 @@ export default function ExercisePickerModal({ multiSelect = false, onSelectExercises = null, enableQuickCreateDraft = false, - /** Planungs-Kontext für KI-Suche (TrainingUnitEditPage o. ä.) */ + /** Gespeicherte training_units.id — aktiviert Planungs-KI (robuster als nur planningContext). */ + planningUnitId = null, + /** true auf TrainingUnitEditPage: Hinweis wenn Einheit noch keine ID hat. */ + expectPlanningSearch = false, + /** Planungs-Kontext für KI-Suche (Abschnitt, Anker, Plan …) */ planningContext = null, /** Wenn gesetzt: z. B. ['simple'] oder ['combination'] — sonst alle Übungsarten */ exerciseKindAny = undefined, @@ -73,7 +77,30 @@ export default function ExercisePickerModal({ const [planningIntentResolved, setPlanningIntentResolved] = useState(null) const pickerScrollRef = useRef(null) - const usePlanningSearch = Boolean(planningContext?.unitId && Number(planningContext.unitId) > 0) + const resolvedPlanningUnitId = useMemo(() => { + const raw = planningUnitId ?? planningContext?.unitId + const id = Number(raw) + return Number.isFinite(id) && id > 0 ? id : null + }, [planningUnitId, planningContext?.unitId]) + + const activePlanningContext = useMemo(() => { + if (!resolvedPlanningUnitId) return null + return { + unitId: resolvedPlanningUnitId, + sectionOrderIndex: planningContext?.sectionOrderIndex ?? 0, + phaseOrderIndex: planningContext?.phaseOrderIndex ?? null, + parallelStreamOrderIndex: planningContext?.parallelStreamOrderIndex ?? null, + anchorExerciseId: planningContext?.anchorExerciseId ?? null, + progressionGraphId: planningContext?.progressionGraphId ?? null, + plannedExerciseIds: Array.isArray(planningContext?.plannedExerciseIds) + ? planningContext.plannedExerciseIds + : [], + intentHint: planningContext?.intentHint ?? null, + } + }, [resolvedPlanningUnitId, planningContext]) + + const usePlanningSearch = resolvedPlanningUnitId != null + const planningSearchBlocked = Boolean(expectPlanningSearch && !usePlanningSearch) /** Gemeinsamer Suchtext — in Planung nur ein Feld; in Bibliothek beide Felder kombiniert. */ const effectivePickerQuery = useMemo(() => { @@ -264,32 +291,54 @@ export default function ExercisePickerModal({ const reload = useCallback(async () => { if (!open || !catalogsReady) return + if (planningSearchBlocked) { + setList([]) + setHasMore(false) + setPlanningContextSummary(null) + setPlanningTargetProfileSummary(null) + setPlanningLlmRankApplied(false) + setPlanningQueryIntentSummary(null) + setPlanningIntentResolved(null) + setLoading(false) + return + } setLoading(true) try { - if (usePlanningSearch) { + if (usePlanningSearch && activePlanningContext) { const query = effectivePickerQuery const res = await api.suggestPlanningExercises({ - unit_id: Number(planningContext.unitId), + unit_id: Number(activePlanningContext.unitId), section_order_index: - planningContext.sectionOrderIndex != null ? Number(planningContext.sectionOrderIndex) : null, + activePlanningContext.sectionOrderIndex != null + ? Number(activePlanningContext.sectionOrderIndex) + : null, phase_order_index: - planningContext.phaseOrderIndex != null ? Number(planningContext.phaseOrderIndex) : null, + activePlanningContext.phaseOrderIndex != null + ? Number(activePlanningContext.phaseOrderIndex) + : null, parallel_stream_order_index: - planningContext.parallelStreamOrderIndex != null - ? Number(planningContext.parallelStreamOrderIndex) + activePlanningContext.parallelStreamOrderIndex != null + ? Number(activePlanningContext.parallelStreamOrderIndex) : null, anchor_exercise_id: - planningContext.anchorExerciseId != null ? Number(planningContext.anchorExerciseId) : null, + activePlanningContext.anchorExerciseId != null + ? Number(activePlanningContext.anchorExerciseId) + : null, progression_graph_id: - planningContext.progressionGraphId != null ? Number(planningContext.progressionGraphId) : null, + activePlanningContext.progressionGraphId != null + ? Number(activePlanningContext.progressionGraphId) + : null, planned_exercise_ids: - Array.isArray(planningContext.plannedExerciseIds) && planningContext.plannedExerciseIds.length > 0 - ? planningContext.plannedExerciseIds.map((x) => Number(x)).filter((x) => Number.isFinite(x) && x > 0) + Array.isArray(activePlanningContext.plannedExerciseIds) && + activePlanningContext.plannedExerciseIds.length > 0 + ? activePlanningContext.plannedExerciseIds + .map((x) => Number(x)) + .filter((x) => Number.isFinite(x) && x > 0) : undefined, include_llm_intent: Boolean(query), include_llm_rank: true, query, - intent_hint: planningContext.intentHint || null, + intent_hint: activePlanningContext.intentHint || null, limit: PAGE_SIZE, exercise_kind_any: Array.isArray(exerciseKindAny) && exerciseKindAny.length > 0 ? exerciseKindAny : undefined, @@ -344,7 +393,8 @@ export default function ExercisePickerModal({ catalogsReady, queryBase, usePlanningSearch, - planningContext, + planningSearchBlocked, + activePlanningContext, effectivePickerQuery, exerciseKindAny, ]) @@ -580,7 +630,25 @@ export default function ExercisePickerModal({ ) : null} ) : null} - {!usePlanningSearch ? ( + {planningSearchBlocked ? ( +

+ Planungs-KI noch nicht verfügbar. Die Einheit hat noch keine gespeicherte ID — + bitte zuerst Speichern, dann den Übungspicker erneut öffnen. Bis dahin gilt nur die + Bibliothekssuche (Volltext). +

+ ) : null} + {!usePlanningSearch && !planningSearchBlocked ? (

0 ? unitId : null) + } planningContext={exercisePickerPlanningContext} onClose={() => { setExercisePickerOpen(false)