diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp index 3e9453b..b16d566 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp @@ -1029,6 +1029,24 @@ void UHyperTwistCoachDashboardWidget::EnsureDefaultDashboardBuilt() TEXT("CoachGuidanceReviewHeader"), 2 ); + GuidanceRationaleHeaderTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachGuidanceRationaleHeader"), + 2 + ); + GuidanceRationaleStatusTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachGuidanceRationaleStatus"), + 2 + ); + GuidanceRationaleDetailTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( + WidgetTree, + RootLayout, + TEXT("CoachGuidanceRationaleDetail"), + 8 + ); GuidancePreviewSelectionTextBlock = HyperTwistCoachDashboardWidgetInternal::AddTextRow( WidgetTree, RootLayout, @@ -1957,6 +1975,23 @@ const FHyperTwistTrainingDeck* UHyperTwistCoachDashboardWidget::GetActiveGuidanc } } +const FHyperTwistCoachBrief* UHyperTwistCoachDashboardWidget::GetActiveGuidancePreviewBrief() const +{ + switch (ActiveGuidancePreviewLane) + { + case EHyperTwistCoachDashboardGuidancePreviewLane::Recommended: + return CachedCoachBrief.Headline.IsEmpty() && CachedCoachBrief.PrimaryActionLabel.IsEmpty() + ? nullptr + : &CachedCoachBrief; + case EHyperTwistCoachDashboardGuidancePreviewLane::FollowUp: + return CachedCoachFollowUpBrief.Headline.IsEmpty() && CachedCoachFollowUpBrief.PrimaryActionLabel.IsEmpty() + ? nullptr + : &CachedCoachFollowUpBrief; + default: + return nullptr; + } +} + FHyperTwistTrainingDeck UHyperTwistCoachDashboardWidget::BuildSelectedGuidancePreviewCaseDeck() const { const FHyperTwistTrainingDeck* Deck = GetActiveGuidancePreviewDeck(); @@ -2313,6 +2348,7 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() && SelectedGuidancePreviewCaseIndex < ActivePreviewDeck->Cases.Num() ? &ActivePreviewDeck->Cases[SelectedGuidancePreviewCaseIndex] : nullptr; + const FHyperTwistCoachBrief* ActivePreviewBrief = GetActiveGuidancePreviewBrief(); const FString ActivePreviewLaneLabel = ActiveGuidancePreviewLane == EHyperTwistCoachDashboardGuidancePreviewLane::Recommended ? TEXT("recommended") @@ -2342,6 +2378,84 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() SelectedGuidancePreviewCase->CanonicalNotation.IsEmpty() ? TEXT("n/a") : *SelectedGuidancePreviewCase->CanonicalNotation ) : TEXT("Preview case: none selected yet."); + bool bSelectedPreviewCaseIsExplicitFocus = false; + int32 SelectedPreviewCaseSignalCount = 0; + FString SelectedPreviewCaseSignalLine = TEXT("none"); + FString SignalSummaryLine = TEXT("none"); + if (ActivePreviewBrief != nullptr) + { + if (SelectedGuidancePreviewCase != nullptr) + { + bSelectedPreviewCaseIsExplicitFocus = + ActivePreviewBrief->FocusCaseIds.Contains(SelectedGuidancePreviewCase->CaseId); + } + + TArray SignalSummaryParts; + SignalSummaryParts.Reserve(FMath::Min(ActivePreviewBrief->Signals.Num(), 2)); + for (const FHyperTwistCoachSignal& Signal : ActivePreviewBrief->Signals) + { + if (SelectedGuidancePreviewCase != nullptr + && Signal.SuggestedCaseIds.Contains(SelectedGuidancePreviewCase->CaseId)) + { + ++SelectedPreviewCaseSignalCount; + if (SelectedPreviewCaseSignalLine == TEXT("none")) + { + SelectedPreviewCaseSignalLine = FString::Printf( + TEXT("%s | %s"), + Signal.Headline.IsEmpty() ? TEXT("signal") : *Signal.Headline, + Signal.Detail.IsEmpty() ? TEXT("no detail") : *Signal.Detail + ); + } + } + + if (SignalSummaryParts.Num() < 2) + { + SignalSummaryParts.Add(FString::Printf( + TEXT("%s (%.2f)"), + Signal.Headline.IsEmpty() ? TEXT("signal") : *Signal.Headline, + Signal.Score + )); + } + } + + if (SignalSummaryParts.Num() > 0) + { + SignalSummaryLine = FString::Join(SignalSummaryParts, TEXT(" | ")); + } + } + const FString GuidanceRationaleStatusLine = + ActivePreviewBrief != nullptr + ? FString::Printf( + TEXT("Rationale: %s lane | Priority: %s (%.2f) | Focus: %s | Mode: %s | Selection: %s"), + *ActivePreviewLaneLabel, + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ActivePreviewBrief->Priority), + ActivePreviewBrief->PriorityScore, + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ActivePreviewBrief->FocusLane), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ActivePreviewBrief->SuggestedMode), + *HyperTwistCoachDashboardWidgetInternal::EnumDisplayName(ActivePreviewBrief->SuggestedSelectionPolicy) + ) + : FString::Printf( + TEXT("Rationale: %s lane | build a recommended or follow-up preview to inspect the coach rationale"), + *ActivePreviewLaneLabel + ); + const FString GuidanceRationaleDetailLine = + ActivePreviewBrief != nullptr + ? FString::Printf( + TEXT("Brief: %s | Action: %s | Deck: %s | Method: %s | Focus cases: %d | Review: %s | Cadence: %s | Variety: %s | Selected focus: %s | Matching signals: %d | Case signal: %s | Top signals: %s"), + ActivePreviewBrief->Headline.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->Headline, + ActivePreviewBrief->PrimaryActionLabel.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->PrimaryActionLabel, + ActivePreviewBrief->FocusDeckId.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->FocusDeckId, + ActivePreviewBrief->FocusMethodSegmentId.IsEmpty() ? TEXT("n/a") : *ActivePreviewBrief->FocusMethodSegmentId, + ActivePreviewBrief->FocusCaseIds.Num(), + ActivePreviewBrief->bNeedsCoachReview ? TEXT("yes") : TEXT("no"), + ActivePreviewBrief->bNeedsCadenceRecovery ? TEXT("yes") : TEXT("no"), + ActivePreviewBrief->bNeedsVarietyRotation ? TEXT("yes") : TEXT("no"), + bSelectedPreviewCaseIsExplicitFocus ? TEXT("yes") : TEXT("no"), + SelectedPreviewCaseSignalCount, + *SelectedPreviewCaseSignalLine, + *SignalSummaryLine + ) + : TEXT("Rationale detail: unavailable until a guidance preview lane is active."); const bool bCanStartPreviewDeck = !bHasLiveAttemptTimer && !bHasActiveRun && ActivePreviewDeck != nullptr && ActivePreviewDeck->IsStructurallyValid(); const bool bCanStartPreviewCase = bCanStartPreviewDeck && SelectedGuidancePreviewCase != nullptr; @@ -2486,6 +2600,18 @@ void UHyperTwistCoachDashboardWidget::UpdateDashboardPresentation() { GuidanceReviewHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Review]"))); } + if (GuidanceRationaleHeaderTextBlock != nullptr) + { + GuidanceRationaleHeaderTextBlock->SetText(FText::FromString(TEXT("[Guidance Rationale]"))); + } + if (GuidanceRationaleStatusTextBlock != nullptr) + { + GuidanceRationaleStatusTextBlock->SetText(FText::FromString(GuidanceRationaleStatusLine)); + } + if (GuidanceRationaleDetailTextBlock != nullptr) + { + GuidanceRationaleDetailTextBlock->SetText(FText::FromString(GuidanceRationaleDetailLine)); + } if (GuidancePreviewSelectionTextBlock != nullptr) { GuidancePreviewSelectionTextBlock->SetText(FText::FromString(GuidancePreviewSelectionLine)); diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h index 1cb82d8..964522e 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistCoachDashboardWidget.h @@ -197,6 +197,15 @@ protected: UPROPERTY(Transient) TObjectPtr GuidanceReviewHeaderTextBlock = nullptr; + UPROPERTY(Transient) + TObjectPtr GuidanceRationaleHeaderTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr GuidanceRationaleStatusTextBlock = nullptr; + + UPROPERTY(Transient) + TObjectPtr GuidanceRationaleDetailTextBlock = nullptr; + UPROPERTY(Transient) TObjectPtr GuidancePreviewSelectionTextBlock = nullptr; @@ -603,6 +612,7 @@ private: const FHyperTwistTrainingCoachSessionQueueStateEntry* FindSelectedQueueEntry() const; const FHyperTwistTrainingTimingPolicy* FindActiveTimingPolicy() const; const FHyperTwistTrainingDeck* GetActiveGuidancePreviewDeck() const; + const FHyperTwistCoachBrief* GetActiveGuidancePreviewBrief() const; FHyperTwistTrainingDeck BuildSelectedGuidancePreviewCaseDeck() const; bool IsLiveAttemptInspectionPhase() const; int32 GetMaxManualSplitCaptureCount() const;