Update version to 0.8.217 and enhance Exercise Progression Path Builder with planning roadmap features
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Incremented application version to 0.8.217 to reflect recent changes. - Added support for a planning roadmap in the Exercise Progression Path Builder, allowing users to save and load structured planning artifacts. - Enhanced the persistence logic for the planning roadmap, ensuring updates are correctly handled during graph modifications. - Improved the user interface to display saved planning hints, enriching the user experience and interaction with the progression graphs.
This commit is contained in:
parent
98b279fa89
commit
5692931d07
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- Migration 088: Planungs-Roadmap-Artefakt am Progressionsgraph (JSONB, optional).
|
||||||
|
-- Speichert Ziel, Start/Ziel, progression_roadmap + stage_specs für Wiederaufnahme der KI-Planung.
|
||||||
|
|
||||||
|
ALTER TABLE exercise_progression_graphs
|
||||||
|
ADD COLUMN IF NOT EXISTS planning_roadmap JSONB;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN exercise_progression_graphs.planning_roadmap IS
|
||||||
|
'Optionales Planungs-Artefakt (goal_query, resolved_structured, progression_roadmap, stage_specs) — Schema v1 im App-Code.';
|
||||||
49
backend/progression_graph_planning_artifact.py
Normal file
49
backend/progression_graph_planning_artifact.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
"""Validierung und Normalisierung des Planungs-Artefakts am Progressionsgraph."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
ARTIFACT_SCHEMA_VERSION = 1
|
||||||
|
_MAX_JSON_BYTES = 64_000
|
||||||
|
|
||||||
|
|
||||||
|
class GraphPlanningRoadmapArtifact(BaseModel):
|
||||||
|
schema_version: int = Field(default=ARTIFACT_SCHEMA_VERSION, ge=1, le=1)
|
||||||
|
goal_query: str = Field(default="", max_length=2000)
|
||||||
|
start_situation: Optional[str] = Field(default=None, max_length=2000)
|
||||||
|
target_state: Optional[str] = Field(default=None, max_length=2000)
|
||||||
|
roadmap_notes: Optional[str] = Field(default=None, max_length=2000)
|
||||||
|
max_steps: int = Field(default=5, ge=2, le=10)
|
||||||
|
progression_roadmap: Optional[Dict[str, Any]] = None
|
||||||
|
path_skill_expectations: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
@field_validator("progression_roadmap", "path_skill_expectations", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def _empty_dict_to_none(cls, v):
|
||||||
|
if v == {}:
|
||||||
|
return None
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_planning_roadmap_payload(raw: Any) -> Optional[Dict[str, Any]]:
|
||||||
|
"""None erlaubt (löschen); sonst validiertes Dict."""
|
||||||
|
if raw is None:
|
||||||
|
return None
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
raise ValueError("planning_roadmap muss ein JSON-Objekt sein")
|
||||||
|
artifact = GraphPlanningRoadmapArtifact.model_validate(raw)
|
||||||
|
out = artifact.model_dump(exclude_none=True)
|
||||||
|
blob = json.dumps(out, ensure_ascii=False)
|
||||||
|
if len(blob.encode("utf-8")) > _MAX_JSON_BYTES:
|
||||||
|
raise ValueError("planning_roadmap ist zu groß (max. 64 KB)")
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ARTIFACT_SCHEMA_VERSION",
|
||||||
|
"GraphPlanningRoadmapArtifact",
|
||||||
|
"normalize_planning_roadmap_payload",
|
||||||
|
]
|
||||||
|
|
@ -3,13 +3,15 @@ Progressionsgraph zwischen Übungen (Übung → Übung), Migration 032–034.
|
||||||
Optional Übungsvarianten als Knoten-Endpunkte; Sequenz-Bulk-Anlage.
|
Optional Übungsvarianten als Knoten-Endpunkte; Sequenz-Bulk-Anlage.
|
||||||
AuthZ analog training_plan_templates: Bearbeiten/Löschen wie Übungen; Governance-Übergänge zentral.
|
AuthZ analog training_plan_templates: Bearbeiten/Löschen wie Übungen; Governance-Übergänge zentral.
|
||||||
"""
|
"""
|
||||||
from typing import Any, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
from pydantic import BaseModel, Field, model_validator
|
from pydantic import BaseModel, Field, model_validator
|
||||||
from psycopg2 import IntegrityError
|
from psycopg2 import IntegrityError
|
||||||
|
from psycopg2.extras import Json
|
||||||
|
|
||||||
from db import get_db, get_cursor, r2d
|
from db import get_db, get_cursor, r2d
|
||||||
|
from progression_graph_planning_artifact import normalize_planning_roadmap_payload
|
||||||
from tenant_context import TenantContext, get_tenant_context, library_content_visibility_sql
|
from tenant_context import TenantContext, get_tenant_context, library_content_visibility_sql
|
||||||
from club_tenancy import (
|
from club_tenancy import (
|
||||||
assert_library_content_deletable,
|
assert_library_content_deletable,
|
||||||
|
|
@ -36,6 +38,7 @@ class ProgressionGraphUpdate(BaseModel):
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
visibility: Optional[str] = Field(None, pattern="^(private|club|official)$")
|
visibility: Optional[str] = Field(None, pattern="^(private|club|official)$")
|
||||||
club_id: Optional[int] = None
|
club_id: Optional[int] = None
|
||||||
|
planning_roadmap: Optional[Dict[str, Any]] = None
|
||||||
|
|
||||||
|
|
||||||
class ProgressionEdgeCreate(BaseModel):
|
class ProgressionEdgeCreate(BaseModel):
|
||||||
|
|
@ -59,6 +62,7 @@ class SequenceStep(BaseModel):
|
||||||
class ProgressionSequenceCreate(BaseModel):
|
class ProgressionSequenceCreate(BaseModel):
|
||||||
steps: List[SequenceStep] = Field(..., min_length=2)
|
steps: List[SequenceStep] = Field(..., min_length=2)
|
||||||
segment_notes: Optional[List[Optional[str]]] = None
|
segment_notes: Optional[List[Optional[str]]] = None
|
||||||
|
planning_roadmap: Optional[Dict[str, Any]] = None
|
||||||
"""Länge muss len(steps)-1 sein, wenn gesetzt; Notiz pro Kante Zwischen je zwei Schritten."""
|
"""Länge muss len(steps)-1 sein, wenn gesetzt; Notiz pro Kante Zwischen je zwei Schritten."""
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@model_validator(mode="after")
|
||||||
|
|
@ -116,6 +120,17 @@ def _assert_graph_writable(cur, row: dict, profile_id: int, role: str) -> None:
|
||||||
assert_library_content_editable(cur, profile_id, role, row)
|
assert_library_content_editable(cur, profile_id, role, row)
|
||||||
|
|
||||||
|
|
||||||
|
def _persist_graph_planning_roadmap(cur, graph_id: int, raw: Optional[Dict[str, Any]]) -> None:
|
||||||
|
try:
|
||||||
|
normalized = normalize_planning_roadmap_payload(raw)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
|
cur.execute(
|
||||||
|
"UPDATE exercise_progression_graphs SET planning_roadmap = %s WHERE id = %s",
|
||||||
|
(Json(normalized) if normalized is not None else None, graph_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _require_graph_read(cur, graph_id: int, profile_id: int, role: str) -> dict:
|
def _require_graph_read(cur, graph_id: int, profile_id: int, role: str) -> dict:
|
||||||
row = _graph_row(cur, graph_id)
|
row = _graph_row(cur, graph_id)
|
||||||
_assert_graph_readable(cur, row, profile_id, role)
|
_assert_graph_readable(cur, row, profile_id, role)
|
||||||
|
|
@ -353,15 +368,24 @@ def update_progression_graph(
|
||||||
fields.append("club_id = %s")
|
fields.append("club_id = %s")
|
||||||
params.append(next_club if next_vis == "club" else None)
|
params.append(next_club if next_vis == "club" else None)
|
||||||
|
|
||||||
if not fields:
|
if "planning_roadmap" in original:
|
||||||
|
_persist_graph_planning_roadmap(cur, graph_id, original.get("planning_roadmap"))
|
||||||
|
|
||||||
|
if not fields and "planning_roadmap" not in original:
|
||||||
return get_progression_graph(graph_id, include_edges=False, tenant=tenant)
|
return get_progression_graph(graph_id, include_edges=False, tenant=tenant)
|
||||||
|
|
||||||
fields.append("updated_at = NOW()")
|
if fields:
|
||||||
params.append(graph_id)
|
fields.append("updated_at = NOW()")
|
||||||
cur.execute(
|
params.append(graph_id)
|
||||||
f"UPDATE exercise_progression_graphs SET {', '.join(fields)} WHERE id = %s",
|
cur.execute(
|
||||||
tuple(params),
|
f"UPDATE exercise_progression_graphs SET {', '.join(fields)} WHERE id = %s",
|
||||||
)
|
tuple(params),
|
||||||
|
)
|
||||||
|
elif "planning_roadmap" in original:
|
||||||
|
cur.execute(
|
||||||
|
"UPDATE exercise_progression_graphs SET updated_at = NOW() WHERE id = %s",
|
||||||
|
(graph_id,),
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
return get_progression_graph(graph_id, include_edges=False, tenant=tenant)
|
return get_progression_graph(graph_id, include_edges=False, tenant=tenant)
|
||||||
|
|
@ -488,6 +512,12 @@ def create_progression_sequence(
|
||||||
note,
|
note,
|
||||||
)
|
)
|
||||||
created.append(row)
|
created.append(row)
|
||||||
|
if body.planning_roadmap is not None:
|
||||||
|
_persist_graph_planning_roadmap(cur, graph_id, body.planning_roadmap)
|
||||||
|
cur.execute(
|
||||||
|
"UPDATE exercise_progression_graphs SET updated_at = NOW() WHERE id = %s",
|
||||||
|
(graph_id,),
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except IntegrityError as e:
|
except IntegrityError as e:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
|
|
|
||||||
36
backend/tests/test_progression_graph_planning_artifact.py
Normal file
36
backend/tests/test_progression_graph_planning_artifact.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
"""Tests Planungs-Artefakt am Progressionsgraph."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from progression_graph_planning_artifact import (
|
||||||
|
ARTIFACT_SCHEMA_VERSION,
|
||||||
|
normalize_planning_roadmap_payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_planning_roadmap_minimal():
|
||||||
|
out = normalize_planning_roadmap_payload(
|
||||||
|
{
|
||||||
|
"schema_version": ARTIFACT_SCHEMA_VERSION,
|
||||||
|
"goal_query": "Mae Geri Perfektion",
|
||||||
|
"max_steps": 5,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert out["goal_query"] == "Mae Geri Perfektion"
|
||||||
|
assert out["max_steps"] == 5
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_planning_roadmap_with_progression_roadmap():
|
||||||
|
out = normalize_planning_roadmap_payload(
|
||||||
|
{
|
||||||
|
"goal_query": "Kumite Beinarbeit",
|
||||||
|
"progression_roadmap": {
|
||||||
|
"stage_specs": [{"major_step_index": 0, "learning_goal": "Grundstellung"}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert out["progression_roadmap"]["stage_specs"][0]["learning_goal"] == "Grundstellung"
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_rejects_invalid_type():
|
||||||
|
with pytest.raises(ValueError, match="JSON-Objekt"):
|
||||||
|
normalize_planning_roadmap_payload("not-json")
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Shinkan Jinkendo Version Information
|
# Shinkan Jinkendo Version Information
|
||||||
|
|
||||||
APP_VERSION = "0.8.216"
|
APP_VERSION = "0.8.217"
|
||||||
BUILD_DATE = "2026-06-07"
|
BUILD_DATE = "2026-06-07"
|
||||||
DB_SCHEMA_VERSION = "20260607087"
|
DB_SCHEMA_VERSION = "20260607088"
|
||||||
|
|
||||||
MODULE_VERSIONS = {
|
MODULE_VERSIONS = {
|
||||||
"legal_documents": "1.4.0", # Admin: Live-Vorschau pro Abschnitt + modale Vollvorschau (Editor + Dokumentenliste)
|
"legal_documents": "1.4.0", # Admin: Live-Vorschau pro Abschnitt + modale Vollvorschau (Editor + Dokumentenliste)
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,31 @@ function formatExpectedSkillNames(skillExpectations, limit = 4) {
|
||||||
.slice(0, limit)
|
.slice(0, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PLANNING_ARTIFACT_SCHEMA = 1
|
||||||
|
|
||||||
|
function buildPlanningRoadmapArtifactSnapshot({
|
||||||
|
goalQuery,
|
||||||
|
startSituation,
|
||||||
|
targetState,
|
||||||
|
roadmapNotes,
|
||||||
|
maxSteps,
|
||||||
|
progressionRoadmap,
|
||||||
|
pathSkillExpectations,
|
||||||
|
}) {
|
||||||
|
const q = (goalQuery || '').trim()
|
||||||
|
if (!q && !progressionRoadmap) return null
|
||||||
|
return {
|
||||||
|
schema_version: PLANNING_ARTIFACT_SCHEMA,
|
||||||
|
goal_query: q,
|
||||||
|
start_situation: (startSituation || '').trim() || null,
|
||||||
|
target_state: (targetState || '').trim() || null,
|
||||||
|
roadmap_notes: (roadmapNotes || '').trim() || null,
|
||||||
|
max_steps: Number(maxSteps) || 5,
|
||||||
|
progression_roadmap: progressionRoadmap || null,
|
||||||
|
path_skill_expectations: pathSkillExpectations || null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Einfügen wächst den Pfad; Ersetzen (replace_step_index) nicht. */
|
/** Einfügen wächst den Pfad; Ersetzen (replace_step_index) nicht. */
|
||||||
function offerGrowsPath(offer) {
|
function offerGrowsPath(offer) {
|
||||||
const replaceIdx = offer?.replace_step_index
|
const replaceIdx = offer?.replace_step_index
|
||||||
|
|
@ -329,6 +354,93 @@ export default function ExerciseProgressionPathBuilder({
|
||||||
const [gapPrepSupplements, setGapPrepSupplements] = useState('')
|
const [gapPrepSupplements, setGapPrepSupplements] = useState('')
|
||||||
const [gapPrepFocusAreaId, setGapPrepFocusAreaId] = useState('')
|
const [gapPrepFocusAreaId, setGapPrepFocusAreaId] = useState('')
|
||||||
const [gapPrepError, setGapPrepError] = useState('')
|
const [gapPrepError, setGapPrepError] = useState('')
|
||||||
|
const [loadedPlanningHint, setLoadedPlanningHint] = useState(false)
|
||||||
|
|
||||||
|
const buildPlanningArtifact = useCallback(
|
||||||
|
() =>
|
||||||
|
buildPlanningRoadmapArtifactSnapshot({
|
||||||
|
goalQuery,
|
||||||
|
startSituation,
|
||||||
|
targetState,
|
||||||
|
roadmapNotes,
|
||||||
|
maxSteps,
|
||||||
|
progressionRoadmap,
|
||||||
|
pathSkillExpectations,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
goalQuery,
|
||||||
|
startSituation,
|
||||||
|
targetState,
|
||||||
|
roadmapNotes,
|
||||||
|
maxSteps,
|
||||||
|
progressionRoadmap,
|
||||||
|
pathSkillExpectations,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
const persistPlanningRoadmapToGraph = useCallback(async () => {
|
||||||
|
if (!graphId) return
|
||||||
|
const artifact = buildPlanningArtifact()
|
||||||
|
if (!artifact?.goal_query && !artifact?.progression_roadmap) return
|
||||||
|
try {
|
||||||
|
await api.updateExerciseProgressionGraph(Number(graphId), { planning_roadmap: artifact })
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Planungs-Artefakt konnte nicht gespeichert werden', e)
|
||||||
|
}
|
||||||
|
}, [graphId, buildPlanningArtifact])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!graphId) return
|
||||||
|
let cancelled = false
|
||||||
|
setLoadedPlanningHint(false)
|
||||||
|
setPathSteps([])
|
||||||
|
setTargetSummary(null)
|
||||||
|
setSemanticBrief(null)
|
||||||
|
setPathQa(null)
|
||||||
|
setGapFillOffers([])
|
||||||
|
setPathSkillExpectations(null)
|
||||||
|
setEditableMajorSteps([])
|
||||||
|
setProgressionRoadmap(null)
|
||||||
|
setRoadmapDirty(false)
|
||||||
|
setStartTargetAnalyzed(false)
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
api
|
||||||
|
.getExerciseProgressionGraph(Number(graphId))
|
||||||
|
.then((g) => {
|
||||||
|
if (cancelled) return
|
||||||
|
const art = g?.planning_roadmap
|
||||||
|
if (!art) return
|
||||||
|
if (art.goal_query) setGoalQuery(String(art.goal_query))
|
||||||
|
if (art.start_situation) setStartSituation(String(art.start_situation))
|
||||||
|
if (art.target_state) setTargetState(String(art.target_state))
|
||||||
|
if (art.roadmap_notes) setRoadmapNotes(String(art.roadmap_notes))
|
||||||
|
if (art.max_steps) setMaxSteps(Number(art.max_steps))
|
||||||
|
if (art.path_skill_expectations) setPathSkillExpectations(art.path_skill_expectations)
|
||||||
|
if (art.progression_roadmap) {
|
||||||
|
setProgressionRoadmap(art.progression_roadmap)
|
||||||
|
const majors = mapMajorStepsFromApi(art.progression_roadmap)
|
||||||
|
if (majors.length >= 2) {
|
||||||
|
setEditableMajorSteps(majors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
art.start_situation ||
|
||||||
|
art.target_state ||
|
||||||
|
art.progression_roadmap?.resolved_structured
|
||||||
|
) {
|
||||||
|
setStartTargetAnalyzed(true)
|
||||||
|
}
|
||||||
|
setLoadedPlanningHint(true)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.warn(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [graphId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|
@ -783,7 +895,7 @@ export default function ExerciseProgressionPathBuilder({
|
||||||
const res = await api.suggestProgressionPath({
|
const res = await api.suggestProgressionPath({
|
||||||
query: q,
|
query: q,
|
||||||
max_steps: Number(maxSteps),
|
max_steps: Number(maxSteps),
|
||||||
include_llm_intent: false,
|
include_llm_intent: true,
|
||||||
include_path_qa: false,
|
include_path_qa: false,
|
||||||
include_llm_path_qa: false,
|
include_llm_path_qa: false,
|
||||||
include_path_reorder: false,
|
include_path_reorder: false,
|
||||||
|
|
@ -818,6 +930,8 @@ export default function ExerciseProgressionPathBuilder({
|
||||||
setGapFillOffers([])
|
setGapFillOffers([])
|
||||||
setPathSkillExpectations(null)
|
setPathSkillExpectations(null)
|
||||||
setRoadmapDirty(false)
|
setRoadmapDirty(false)
|
||||||
|
setLoadedPlanningHint(false)
|
||||||
|
await persistPlanningRoadmapToGraph()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
setError(e.message || 'Roadmap-Vorschlag fehlgeschlagen')
|
setError(e.message || 'Roadmap-Vorschlag fehlgeschlagen')
|
||||||
|
|
@ -865,6 +979,8 @@ export default function ExerciseProgressionPathBuilder({
|
||||||
})
|
})
|
||||||
applyPathMatchResponse(res, q)
|
applyPathMatchResponse(res, q)
|
||||||
setMaxSteps(validSteps.length)
|
setMaxSteps(validSteps.length)
|
||||||
|
setLoadedPlanningHint(false)
|
||||||
|
await persistPlanningRoadmapToGraph()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
setError(e.message || 'Übungs-Match fehlgeschlagen')
|
setError(e.message || 'Übungs-Match fehlgeschlagen')
|
||||||
|
|
@ -899,12 +1015,14 @@ export default function ExerciseProgressionPathBuilder({
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
setError('')
|
setError('')
|
||||||
try {
|
try {
|
||||||
|
const planningArtifact = buildPlanningArtifact()
|
||||||
await api.createExerciseProgressionSequence(Number(graphId), {
|
await api.createExerciseProgressionSequence(Number(graphId), {
|
||||||
steps: steps.map((s) => ({
|
steps: steps.map((s) => ({
|
||||||
exercise_id: s.exerciseId,
|
exercise_id: s.exerciseId,
|
||||||
variant_id: s.variantId || null,
|
variant_id: s.variantId || null,
|
||||||
})),
|
})),
|
||||||
segment_notes,
|
segment_notes,
|
||||||
|
...(planningArtifact ? { planning_roadmap: planningArtifact } : {}),
|
||||||
})
|
})
|
||||||
setPathSteps([])
|
setPathSteps([])
|
||||||
setTargetSummary(null)
|
setTargetSummary(null)
|
||||||
|
|
@ -942,6 +1060,22 @@ export default function ExerciseProgressionPathBuilder({
|
||||||
Zuerst didaktische Roadmap vorschlagen und anpassen, dann Übungen je Major Step aus der Bibliothek matchen.
|
Zuerst didaktische Roadmap vorschlagen und anpassen, dann Übungen je Major Step aus der Bibliothek matchen.
|
||||||
Lücken können mit KI als Übung angelegt werden.
|
Lücken können mit KI als Übung angelegt werden.
|
||||||
</p>
|
</p>
|
||||||
|
{loadedPlanningHint && editableMajorSteps.length > 0 && pathSteps.length === 0 ? (
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: 'var(--accent-dark)',
|
||||||
|
margin: '0 0 10px',
|
||||||
|
padding: '8px 10px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
background: 'color-mix(in srgb, var(--accent) 8%, var(--surface))',
|
||||||
|
lineHeight: 1.45,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Gespeicherte Planung für diesen Graph geladen — Roadmap anpassen und erneut matchen, oder neuen Vorschlag
|
||||||
|
starten.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'flex-end' }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'flex-end' }}>
|
||||||
<div className="form-row" style={{ flex: '2 1 240px', marginBottom: 0 }}>
|
<div className="form-row" style={{ flex: '2 1 240px', marginBottom: 0 }}>
|
||||||
<label className="form-label">Ziel / Entwicklungsrichtung</label>
|
<label className="form-label">Ziel / Entwicklungsrichtung</label>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user