Optimierung KI-Scuhe + Ki-Überarbeitungen der Übungen #49
|
|
@ -38,7 +38,11 @@ export default function ExercisePickerModal({
|
||||||
multiSelect = false,
|
multiSelect = false,
|
||||||
onSelectExercises = null,
|
onSelectExercises = null,
|
||||||
enableQuickCreateDraft = false,
|
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,
|
planningContext = null,
|
||||||
/** Wenn gesetzt: z. B. ['simple'] oder ['combination'] — sonst alle Übungsarten */
|
/** Wenn gesetzt: z. B. ['simple'] oder ['combination'] — sonst alle Übungsarten */
|
||||||
exerciseKindAny = undefined,
|
exerciseKindAny = undefined,
|
||||||
|
|
@ -73,7 +77,30 @@ export default function ExercisePickerModal({
|
||||||
const [planningIntentResolved, setPlanningIntentResolved] = useState(null)
|
const [planningIntentResolved, setPlanningIntentResolved] = useState(null)
|
||||||
const pickerScrollRef = useRef(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. */
|
/** Gemeinsamer Suchtext — in Planung nur ein Feld; in Bibliothek beide Felder kombiniert. */
|
||||||
const effectivePickerQuery = useMemo(() => {
|
const effectivePickerQuery = useMemo(() => {
|
||||||
|
|
@ -264,32 +291,54 @@ export default function ExercisePickerModal({
|
||||||
|
|
||||||
const reload = useCallback(async () => {
|
const reload = useCallback(async () => {
|
||||||
if (!open || !catalogsReady) return
|
if (!open || !catalogsReady) return
|
||||||
|
if (planningSearchBlocked) {
|
||||||
|
setList([])
|
||||||
|
setHasMore(false)
|
||||||
|
setPlanningContextSummary(null)
|
||||||
|
setPlanningTargetProfileSummary(null)
|
||||||
|
setPlanningLlmRankApplied(false)
|
||||||
|
setPlanningQueryIntentSummary(null)
|
||||||
|
setPlanningIntentResolved(null)
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
if (usePlanningSearch) {
|
if (usePlanningSearch && activePlanningContext) {
|
||||||
const query = effectivePickerQuery
|
const query = effectivePickerQuery
|
||||||
const res = await api.suggestPlanningExercises({
|
const res = await api.suggestPlanningExercises({
|
||||||
unit_id: Number(planningContext.unitId),
|
unit_id: Number(activePlanningContext.unitId),
|
||||||
section_order_index:
|
section_order_index:
|
||||||
planningContext.sectionOrderIndex != null ? Number(planningContext.sectionOrderIndex) : null,
|
activePlanningContext.sectionOrderIndex != null
|
||||||
|
? Number(activePlanningContext.sectionOrderIndex)
|
||||||
|
: null,
|
||||||
phase_order_index:
|
phase_order_index:
|
||||||
planningContext.phaseOrderIndex != null ? Number(planningContext.phaseOrderIndex) : null,
|
activePlanningContext.phaseOrderIndex != null
|
||||||
|
? Number(activePlanningContext.phaseOrderIndex)
|
||||||
|
: null,
|
||||||
parallel_stream_order_index:
|
parallel_stream_order_index:
|
||||||
planningContext.parallelStreamOrderIndex != null
|
activePlanningContext.parallelStreamOrderIndex != null
|
||||||
? Number(planningContext.parallelStreamOrderIndex)
|
? Number(activePlanningContext.parallelStreamOrderIndex)
|
||||||
: null,
|
: null,
|
||||||
anchor_exercise_id:
|
anchor_exercise_id:
|
||||||
planningContext.anchorExerciseId != null ? Number(planningContext.anchorExerciseId) : null,
|
activePlanningContext.anchorExerciseId != null
|
||||||
|
? Number(activePlanningContext.anchorExerciseId)
|
||||||
|
: null,
|
||||||
progression_graph_id:
|
progression_graph_id:
|
||||||
planningContext.progressionGraphId != null ? Number(planningContext.progressionGraphId) : null,
|
activePlanningContext.progressionGraphId != null
|
||||||
|
? Number(activePlanningContext.progressionGraphId)
|
||||||
|
: null,
|
||||||
planned_exercise_ids:
|
planned_exercise_ids:
|
||||||
Array.isArray(planningContext.plannedExerciseIds) && planningContext.plannedExerciseIds.length > 0
|
Array.isArray(activePlanningContext.plannedExerciseIds) &&
|
||||||
? planningContext.plannedExerciseIds.map((x) => Number(x)).filter((x) => Number.isFinite(x) && x > 0)
|
activePlanningContext.plannedExerciseIds.length > 0
|
||||||
|
? activePlanningContext.plannedExerciseIds
|
||||||
|
.map((x) => Number(x))
|
||||||
|
.filter((x) => Number.isFinite(x) && x > 0)
|
||||||
: undefined,
|
: undefined,
|
||||||
include_llm_intent: Boolean(query),
|
include_llm_intent: Boolean(query),
|
||||||
include_llm_rank: true,
|
include_llm_rank: true,
|
||||||
query,
|
query,
|
||||||
intent_hint: planningContext.intentHint || null,
|
intent_hint: activePlanningContext.intentHint || null,
|
||||||
limit: PAGE_SIZE,
|
limit: PAGE_SIZE,
|
||||||
exercise_kind_any:
|
exercise_kind_any:
|
||||||
Array.isArray(exerciseKindAny) && exerciseKindAny.length > 0 ? exerciseKindAny : undefined,
|
Array.isArray(exerciseKindAny) && exerciseKindAny.length > 0 ? exerciseKindAny : undefined,
|
||||||
|
|
@ -344,7 +393,8 @@ export default function ExercisePickerModal({
|
||||||
catalogsReady,
|
catalogsReady,
|
||||||
queryBase,
|
queryBase,
|
||||||
usePlanningSearch,
|
usePlanningSearch,
|
||||||
planningContext,
|
planningSearchBlocked,
|
||||||
|
activePlanningContext,
|
||||||
effectivePickerQuery,
|
effectivePickerQuery,
|
||||||
exerciseKindAny,
|
exerciseKindAny,
|
||||||
])
|
])
|
||||||
|
|
@ -580,7 +630,25 @@ export default function ExercisePickerModal({
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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
|
<p
|
||||||
style={{
|
style={{
|
||||||
margin: '0 0 10px',
|
margin: '0 0 10px',
|
||||||
|
|
|
||||||
|
|
@ -803,6 +803,11 @@ export default function TrainingUnitEditPage() {
|
||||||
open={exercisePickerOpen}
|
open={exercisePickerOpen}
|
||||||
multiSelect
|
multiSelect
|
||||||
enableQuickCreateDraft
|
enableQuickCreateDraft
|
||||||
|
expectPlanningSearch
|
||||||
|
planningUnitId={
|
||||||
|
editingUnit?.id ??
|
||||||
|
(Number.isFinite(unitId) && unitId > 0 ? unitId : null)
|
||||||
|
}
|
||||||
planningContext={exercisePickerPlanningContext}
|
planningContext={exercisePickerPlanningContext}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setExercisePickerOpen(false)
|
setExercisePickerOpen(false)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user