Enhance ExercisePickerModal with Improved Planning Context Handling
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m39s
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m39s
- 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.
This commit is contained in:
parent
d019c20338
commit
f5c886fc13
|
|
@ -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}
|
||||
</div>
|
||||
) : null}
|
||||
{!usePlanningSearch ? (
|
||||
{planningSearchBlocked ? (
|
||||
<p
|
||||
style={{
|
||||
margin: '0 0 10px',
|
||||
padding: '10px 12px',
|
||||
borderRadius: '8px',
|
||||
background: 'color-mix(in srgb, var(--danger) 12%, var(--surface2))',
|
||||
border: '1px solid color-mix(in srgb, var(--danger) 35%, var(--border))',
|
||||
fontSize: '13px',
|
||||
color: 'var(--text1)',
|
||||
lineHeight: 1.45,
|
||||
}}
|
||||
>
|
||||
<strong>Planungs-KI noch nicht verfügbar.</strong> Die Einheit hat noch keine gespeicherte ID —
|
||||
bitte zuerst <strong>Speichern</strong>, dann den Übungspicker erneut öffnen. Bis dahin gilt nur die
|
||||
Bibliothekssuche (Volltext).
|
||||
</p>
|
||||
) : null}
|
||||
{!usePlanningSearch && !planningSearchBlocked ? (
|
||||
<p
|
||||
style={{
|
||||
margin: '0 0 10px',
|
||||
|
|
|
|||
|
|
@ -803,6 +803,11 @@ export default function TrainingUnitEditPage() {
|
|||
open={exercisePickerOpen}
|
||||
multiSelect
|
||||
enableQuickCreateDraft
|
||||
expectPlanningSearch
|
||||
planningUnitId={
|
||||
editingUnit?.id ??
|
||||
(Number.isFinite(unitId) && unitId > 0 ? unitId : null)
|
||||
}
|
||||
planningContext={exercisePickerPlanningContext}
|
||||
onClose={() => {
|
||||
setExercisePickerOpen(false)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user