diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp index 7803f9c..e0192ba 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistBootstrap/HyperTwistContractLibrary.cpp @@ -903,6 +903,65 @@ namespace HyperTwistContractLibraryInternal return Profile; } + FHyperTwistSpeechUsageCostDashboardShellProfile MakeDefaultSpeechUsageCostDashboardShellProfile( + const FString& ProfileId + ) + { + FHyperTwistSpeechUsageCostDashboardShellProfile Profile; + Profile.DashboardShellProfileId = ProfileId; + Profile.ShellKind = TEXT("operator-usage-cost-dashboard"); + Profile.UsageSurfaceMode = TEXT("normalized-usage-summary-card"); + Profile.CostSurfaceMode = TEXT("estimate-first-cost-card"); + Profile.EscalationBannerMode = TEXT("route-and-budget-posture-banner"); + Profile.RefreshActionId = TEXT("refresh-provider-usage-cost-dashboard"); + Profile.RouteInspectActionId = TEXT("inspect-provider-route"); + Profile.BudgetInspectActionId = TEXT("review-provider-budget"); + Profile.bSupportsProviderSummary = true; + Profile.bSupportsEstimateBadges = true; + Profile.bSupportsQuotaEscalationBanner = true; + Profile.bSupportsRouteDiagnostics = true; + + auto AddPanel = [&Profile]( + const TCHAR* PanelId, + const TCHAR* PanelKind, + const TCHAR* AnchorId, + const bool bVisibleByDefault + ) + { + FHyperTwistSpeechShellPanelLayout Panel; + Panel.PanelId = PanelId; + Panel.PanelKind = PanelKind; + Panel.AnchorId = AnchorId; + Panel.bVisibleByDefault = bVisibleByDefault; + Profile.Panels.Add(Panel); + }; + + auto AddAction = [&Profile]( + const TCHAR* ActionId, + const TCHAR* InputBinding, + const TCHAR* SurfaceId + ) + { + FHyperTwistSpeechShellActionBinding ActionBinding; + ActionBinding.ActionId = ActionId; + ActionBinding.InputBinding = InputBinding; + ActionBinding.SurfaceId = SurfaceId; + Profile.ActionBindings.Add(ActionBinding); + }; + + AddPanel(TEXT("provider-summary-card"), TEXT("provider-summary-card"), TEXT("top-left"), true); + AddPanel(TEXT("usage-meter-card"), TEXT("usage-meter-card"), TEXT("left-stack-below-provider"), true); + AddPanel(TEXT("cost-estimate-card"), TEXT("cost-estimate-card"), TEXT("top-right"), true); + AddPanel(TEXT("quota-state-card"), TEXT("quota-state-card"), TEXT("right-stack-below-cost"), true); + AddPanel(TEXT("route-diagnostics-card"), TEXT("route-diagnostics-card"), TEXT("bottom-right"), true); + + AddAction(TEXT("refresh-provider-usage-cost-dashboard"), TEXT("F5"), TEXT("provider-summary-card")); + AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("route-diagnostics-card")); + AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("quota-state-card")); + AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root")); + return Profile; + } + FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor( const TCHAR* PayloadId, const TCHAR* PayloadKind, @@ -981,7 +1040,10 @@ namespace HyperTwistContractLibraryInternal TEXT("promptHints"), TEXT("normalizedUsageEvents"), TEXT("normalizedCostEvents"), - TEXT("quotaRateLimitState") + TEXT("quotaRateLimitState"), + TEXT("providerUsageCostDashboardShell"), + TEXT("providerQuotaEscalationBanner"), + TEXT("providerRouteCostDiagnostics") }; } @@ -2202,6 +2264,11 @@ FHyperTwistSpeechSessionConfig UHyperTwistContractLibrary::MakeSampleSpeechSessi true, false ); + Config.UsageCostDashboardShellProfileId = TEXT("speech-provider-usage-cost-dashboard-shell-v1"); + Config.UsageCostDashboardShellProfileDefinition = + HyperTwistContractLibraryInternal::MakeDefaultSpeechUsageCostDashboardShellProfile( + Config.UsageCostDashboardShellProfileId + ); Config.PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); Config.PayloadCustodyProfileDefinition = HyperTwistContractLibraryInternal::MakeDefaultSpeechPayloadCustodyProfile( @@ -2309,7 +2376,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistContractLibrary::MakeMockSpeechService TEXT("payloadIntegrityReview"), TEXT("normalizedUsageEvents"), TEXT("normalizedCostEvents"), - TEXT("quotaRateLimitState") + TEXT("quotaRateLimitState"), + TEXT("providerUsageCostDashboardShell"), + TEXT("providerQuotaEscalationBanner"), + TEXT("providerRouteCostDiagnostics") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp index 3e2d8da..ac8e972 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistHttpSpeechClient.cpp @@ -341,7 +341,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("payloadIntegrityReview"), TEXT("normalizedUsageEvents"), TEXT("normalizedCostEvents"), - TEXT("quotaRateLimitState") + TEXT("quotaRateLimitState"), + TEXT("providerUsageCostDashboardShell"), + TEXT("providerQuotaEscalationBanner"), + TEXT("providerRouteCostDiagnostics") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") @@ -415,7 +418,10 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal TEXT("payloadIntegrityReview"), TEXT("normalizedUsageEvents"), TEXT("normalizedCostEvents"), - TEXT("quotaRateLimitState") + TEXT("quotaRateLimitState"), + TEXT("providerUsageCostDashboardShell"), + TEXT("providerQuotaEscalationBanner"), + TEXT("providerRouteCostDiagnostics") }; Health.SupportedOrchestrationProfiles = { TEXT("python-batch-transcribe-v1") @@ -474,6 +480,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistHttpSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("normalizedUsageEvents")); Health.Capabilities.AddUnique(TEXT("normalizedCostEvents")); Health.Capabilities.AddUnique(TEXT("quotaRateLimitState")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostDashboardShell")); + Health.Capabilities.AddUnique(TEXT("providerQuotaEscalationBanner")); + Health.Capabilities.AddUnique(TEXT("providerRouteCostDiagnostics")); Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1")); if (Health.ProviderProfileId.IsEmpty()) { diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp index ba50840..2683f8f 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistRecognition/HyperTwistMockSpeechClient.cpp @@ -146,6 +146,9 @@ FHyperTwistSpeechServiceHealth UHyperTwistMockSpeechClient::GetSpeechServiceHeal Health.Capabilities.AddUnique(TEXT("normalizedUsageEvents")); Health.Capabilities.AddUnique(TEXT("normalizedCostEvents")); Health.Capabilities.AddUnique(TEXT("quotaRateLimitState")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostDashboardShell")); + Health.Capabilities.AddUnique(TEXT("providerQuotaEscalationBanner")); + Health.Capabilities.AddUnique(TEXT("providerRouteCostDiagnostics")); Health.SupportedOrchestrationProfiles.AddUnique(TEXT("python-batch-transcribe-v1")); return Health; } diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp index 6b97303..3b4f13d 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp @@ -2916,6 +2916,11 @@ namespace HyperTwistTrainingSubsystemInternal const bool bSessionOpen ); + FHyperTwistSpeechUsageCostDashboardShellState BuildSpeechUsageCostDashboardShellState( + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistTrainingCompanionSpeechSessionState* PriorState + ); + FHyperTwistSpeechMicrophoneShellProfile BuildDefaultCoachCommandMicrophoneShellProfile(const FString& ProfileId) { FHyperTwistSpeechMicrophoneShellProfile Profile; @@ -2978,6 +2983,65 @@ namespace HyperTwistTrainingSubsystemInternal return Profile; } + FHyperTwistSpeechUsageCostDashboardShellProfile BuildDefaultSpeechUsageCostDashboardShellProfile( + const FString& ProfileId + ) + { + FHyperTwistSpeechUsageCostDashboardShellProfile Profile; + Profile.DashboardShellProfileId = ProfileId; + Profile.ShellKind = TEXT("operator-usage-cost-dashboard"); + Profile.UsageSurfaceMode = TEXT("normalized-usage-summary-card"); + Profile.CostSurfaceMode = TEXT("estimate-first-cost-card"); + Profile.EscalationBannerMode = TEXT("route-and-budget-posture-banner"); + Profile.RefreshActionId = TEXT("refresh-provider-usage-cost-dashboard"); + Profile.RouteInspectActionId = TEXT("inspect-provider-route"); + Profile.BudgetInspectActionId = TEXT("review-provider-budget"); + Profile.bSupportsProviderSummary = true; + Profile.bSupportsEstimateBadges = true; + Profile.bSupportsQuotaEscalationBanner = true; + Profile.bSupportsRouteDiagnostics = true; + + auto AddPanel = [&Profile]( + const TCHAR* PanelId, + const TCHAR* PanelKind, + const TCHAR* AnchorId, + const bool bVisibleByDefault + ) + { + FHyperTwistSpeechShellPanelLayout Panel; + Panel.PanelId = PanelId; + Panel.PanelKind = PanelKind; + Panel.AnchorId = AnchorId; + Panel.bVisibleByDefault = bVisibleByDefault; + Profile.Panels.Add(Panel); + }; + + auto AddAction = [&Profile]( + const TCHAR* ActionId, + const TCHAR* InputBinding, + const TCHAR* SurfaceId + ) + { + FHyperTwistSpeechShellActionBinding ActionBinding; + ActionBinding.ActionId = ActionId; + ActionBinding.InputBinding = InputBinding; + ActionBinding.SurfaceId = SurfaceId; + Profile.ActionBindings.Add(ActionBinding); + }; + + AddPanel(TEXT("provider-summary-card"), TEXT("provider-summary-card"), TEXT("top-left"), true); + AddPanel(TEXT("usage-meter-card"), TEXT("usage-meter-card"), TEXT("left-stack-below-provider"), true); + AddPanel(TEXT("cost-estimate-card"), TEXT("cost-estimate-card"), TEXT("top-right"), true); + AddPanel(TEXT("quota-state-card"), TEXT("quota-state-card"), TEXT("right-stack-below-cost"), true); + AddPanel(TEXT("route-diagnostics-card"), TEXT("route-diagnostics-card"), TEXT("bottom-right"), true); + + AddAction(TEXT("refresh-provider-usage-cost-dashboard"), TEXT("F5"), TEXT("provider-summary-card")); + AddAction(TEXT("inspect-provider-route"), TEXT("P"), TEXT("route-diagnostics-card")); + AddAction(TEXT("review-provider-budget"), TEXT("B"), TEXT("quota-state-card")); + AddAction(TEXT("close-speech-session"), TEXT("Escape"), TEXT("session-root")); + return Profile; + } + FHyperTwistSpeechModelPayloadDescriptor MakeSpeechModelPayloadDescriptor( const TCHAR* PayloadId, const TCHAR* PayloadKind, @@ -3056,7 +3120,10 @@ namespace HyperTwistTrainingSubsystemInternal TEXT("promptHints"), TEXT("normalizedUsageEvents"), TEXT("normalizedCostEvents"), - TEXT("quotaRateLimitState") + TEXT("quotaRateLimitState"), + TEXT("providerUsageCostDashboardShell"), + TEXT("providerQuotaEscalationBanner"), + TEXT("providerRouteCostDiagnostics") }; } @@ -3691,6 +3758,26 @@ namespace HyperTwistTrainingSubsystemInternal false ); } + if (SessionConfig.UsageCostDashboardShellProfileId.IsEmpty() + && SessionConfig.UsageCostDashboardShellProfileDefinition.IsStructurallyValid()) + { + SessionConfig.UsageCostDashboardShellProfileId = + SessionConfig.UsageCostDashboardShellProfileDefinition.DashboardShellProfileId; + } + if (SessionConfig.UsageCostDashboardShellProfileId.IsEmpty()) + { + SessionConfig.UsageCostDashboardShellProfileId = + TEXT("speech-provider-usage-cost-dashboard-shell-v1"); + } + if (!SessionConfig.UsageCostDashboardShellProfileDefinition.IsStructurallyValid()) + { + SessionConfig.UsageCostDashboardShellProfileDefinition = + BuildDefaultSpeechUsageCostDashboardShellProfile( + SessionConfig.UsageCostDashboardShellProfileId + ); + } + SessionConfig.UsageCostDashboardShellProfileId = + SessionConfig.UsageCostDashboardShellProfileDefinition.DashboardShellProfileId; } void ApplySpeechProviderProfileHealthDefaults( @@ -4020,6 +4107,9 @@ namespace HyperTwistTrainingSubsystemInternal Health.Capabilities.AddUnique(TEXT("normalizedUsageEvents")); Health.Capabilities.AddUnique(TEXT("normalizedCostEvents")); Health.Capabilities.AddUnique(TEXT("quotaRateLimitState")); + Health.Capabilities.AddUnique(TEXT("providerUsageCostDashboardShell")); + Health.Capabilities.AddUnique(TEXT("providerQuotaEscalationBanner")); + Health.Capabilities.AddUnique(TEXT("providerRouteCostDiagnostics")); } bool IsSpeechPermissionError(const FString& ErrorText) @@ -4241,6 +4331,279 @@ namespace HyperTwistTrainingSubsystemInternal ); } + void AddSpeechUsageCostDashboardIssue( + FHyperTwistSpeechUsageCostDashboardShellState& ShellState, + const FHyperTwistSpeechUsageCostDashboardIssue& Issue + ) + { + if (!Issue.IsStructurallyValid()) + { + return; + } + + ShellState.Issues.Add(Issue); + if (!ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.ActiveIssue = Issue; + } + } + + void ApplySpeechUsageCostDashboardPosture( + const FHyperTwistSpeechUsageCostDashboardShellProfile& Profile, + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistSpeechServiceHealth& ServiceHealth, + const FString& LastError, + FHyperTwistSpeechUsageCostDashboardShellState& ShellState + ) + { + const FHyperTwistSpeechProviderProfile EffectiveProfile = + ServiceHealth.ProviderProfileDefinition.IsStructurallyValid() + ? ServiceHealth.ProviderProfileDefinition + : (SessionConfig.ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderProfileDefinition + : BuildDefaultHttpSpeechProviderProfile( + TEXT("Local HTTP Speech Sidecar"), + TEXT("http://127.0.0.1:8766") + )); + const FHyperTwistSpeechProviderRouteDecision EffectiveRouteDecision = + ServiceHealth.ProviderRouteDecision.IsStructurallyValid() + ? ServiceHealth.ProviderRouteDecision + : SessionConfig.ProviderRouteDecision; + const FHyperTwistSpeechNormalizedUsageEvent EffectiveUsageEvent = + ServiceHealth.LatestUsageEvent.IsStructurallyValid() + ? ServiceHealth.LatestUsageEvent + : SessionConfig.UsageEventTemplate; + const FHyperTwistSpeechNormalizedCostEvent EffectiveCostEvent = + ServiceHealth.LatestCostEvent.IsStructurallyValid() + ? ServiceHealth.LatestCostEvent + : SessionConfig.CostEventTemplate; + const FHyperTwistSpeechQuotaRateLimitState EffectiveQuotaState = + ServiceHealth.QuotaRateLimitState.IsStructurallyValid() + ? ServiceHealth.QuotaRateLimitState + : BuildDefaultSpeechQuotaRateLimitState( + SessionConfig.UsageCostAccountingProfileDefinition.IsStructurallyValid() + ? SessionConfig.UsageCostAccountingProfileDefinition + : BuildDefaultSpeechUsageCostAccountingProfile( + TEXT("speech-usage-cost-accounting-v1") + ), + EffectiveProfile, + !EffectiveRouteDecision.SelectedServiceLaneId.IsEmpty() + ? EffectiveRouteDecision.SelectedServiceLaneId + : TEXT("speech/python-service"), + TEXT("within-budget"), + true, + false, + false, + 60, + 3600, + 25.0f + ); + const FString EffectiveError = !LastError.IsEmpty() + ? LastError + : (!ServiceHealth.LastError.IsEmpty() + ? ServiceHealth.LastError + : EffectiveRouteDecision.FailureReason); + + ShellState.ActiveProviderProfileId = EffectiveProfile.ProviderProfileId; + ShellState.ActiveProviderDisplayLabel = !EffectiveProfile.DisplayLabel.IsEmpty() + ? EffectiveProfile.DisplayLabel + : EffectiveProfile.ProviderProfileId; + ShellState.ActiveServiceLaneId = !EffectiveRouteDecision.SelectedServiceLaneId.IsEmpty() + ? EffectiveRouteDecision.SelectedServiceLaneId + : (!SessionConfig.OrchestrationProfile.ServiceLaneId.IsEmpty() + ? SessionConfig.OrchestrationProfile.ServiceLaneId + : TEXT("speech/python-service")); + ShellState.ActiveRouteStateId = !EffectiveRouteDecision.RouteStateId.IsEmpty() + ? EffectiveRouteDecision.RouteStateId + : (ServiceHealth.bReady ? TEXT("route-ready") : TEXT("route-health-unknown")); + ShellState.UsageAggregationWindowId = !EffectiveUsageEvent.AggregationWindowId.IsEmpty() + ? EffectiveUsageEvent.AggregationWindowId + : TEXT("service-health-window"); + ShellState.CostAggregationWindowId = !EffectiveCostEvent.AggregationWindowId.IsEmpty() + ? EffectiveCostEvent.AggregationWindowId + : TEXT("service-health-window"); + ShellState.UsageMeterKind = !EffectiveUsageEvent.MeterKind.IsEmpty() + ? EffectiveUsageEvent.MeterKind + : TEXT("audio-seconds"); + ShellState.CurrencyCode = !EffectiveCostEvent.CurrencyCode.IsEmpty() + ? EffectiveCostEvent.CurrencyCode + : TEXT("USD"); + ShellState.LatestUsageEventId = !EffectiveUsageEvent.UsageEventId.IsEmpty() + ? EffectiveUsageEvent.UsageEventId + : TEXT("speech-usage-event/unavailable"); + ShellState.LatestCostEventId = !EffectiveCostEvent.CostEventId.IsEmpty() + ? EffectiveCostEvent.CostEventId + : TEXT("speech-cost-event/unavailable"); + ShellState.LatestQuotaStateId = !EffectiveQuotaState.QuotaStateId.IsEmpty() + ? EffectiveQuotaState.QuotaStateId + : TEXT("speech-quota-state/unavailable"); + ShellState.LatestRouteDecisionId = !EffectiveRouteDecision.RouteDecisionId.IsEmpty() + ? EffectiveRouteDecision.RouteDecisionId + : TEXT("speech-route-decision/unavailable"); + ShellState.DisplayedUsageQuantity = FMath::Max(0.0f, EffectiveUsageEvent.ReportedQuantity); + ShellState.DisplayedEstimatedCostUsd = FMath::Max(0.0f, EffectiveCostEvent.EstimatedCost); + ShellState.DisplayedReportedCostUsd = FMath::Max(0.0f, EffectiveCostEvent.ReportedCost); + ShellState.RemainingEstimatedSpendUsd = + FMath::Max(0.0f, EffectiveQuotaState.RemainingEstimatedSpendUsd); + ShellState.RemainingRequestCount = FMath::Max(0, EffectiveQuotaState.RemainingRequestCount); + ShellState.RemainingAudioSeconds = FMath::Max(0, EffectiveQuotaState.RemainingAudioSeconds); + ShellState.bDashboardVisible = true; + ShellState.bProviderSummaryVisible = Profile.bSupportsProviderSummary; + ShellState.bUsageCardVisible = true; + ShellState.bCostCardVisible = true; + ShellState.bQuotaCardVisible = true; + ShellState.bRouteDiagnosticsVisible = Profile.bSupportsRouteDiagnostics; + ShellState.bProviderReceiptPosted = EffectiveCostEvent.bHasProviderPostedCharge; + ShellState.bEstimateBadgeVisible = + Profile.bSupportsEstimateBadges + && (EffectiveUsageEvent.bEstimateOnly || EffectiveCostEvent.bEstimateOnly); + ShellState.bQuotaKnown = EffectiveQuotaState.bQuotaKnown; + ShellState.bRateLimited = EffectiveQuotaState.bRateLimited; + ShellState.bHardBlocked = EffectiveQuotaState.bHardBlocked; + ShellState.bRouteReady = EffectiveRouteDecision.IsStructurallyValid() + ? EffectiveRouteDecision.bRouteReady + : ServiceHealth.bReady; + ShellState.Issues.Reset(); + ShellState.ActiveIssue = FHyperTwistSpeechUsageCostDashboardIssue(); + + ShellState.StatusLine = TEXT("Provider usage and budget posture within current bounds."); + ShellState.DetailLine = FString::Printf( + TEXT("%s reports %.2f %s and %.2f %s estimated cost in %s."), + *ShellState.ActiveProviderDisplayLabel, + ShellState.DisplayedUsageQuantity, + *ShellState.UsageMeterKind, + ShellState.DisplayedEstimatedCostUsd, + *ShellState.CurrencyCode, + *ShellState.UsageAggregationWindowId + ); + ShellState.bEscalationBannerVisible = false; + + if (!ShellState.bRouteReady || !ServiceHealth.bReady || !EffectiveError.IsEmpty()) + { + FHyperTwistSpeechUsageCostDashboardIssue Issue; + Issue.IssueId = TEXT("provider-route-degraded"); + Issue.IssueKind = TEXT("route-degraded"); + Issue.StatusLine = TEXT("Provider route requires attention."); + Issue.DetailLine = EffectiveError.IsEmpty() + ? TEXT("The provider route is not currently ready. Inspect the active route before relying on usage or cost posture.") + : FString::Printf( + TEXT("The provider route reported '%s'. Inspect the active route before relying on usage or cost posture."), + *EffectiveError + ); + Issue.RecommendedActionId = Profile.RouteInspectActionId; + AddSpeechUsageCostDashboardIssue(ShellState, Issue); + ShellState.StatusLine = Issue.StatusLine; + ShellState.DetailLine = Issue.DetailLine; + ShellState.bEscalationBannerVisible = Profile.bSupportsQuotaEscalationBanner; + } + + if (ShellState.bHardBlocked) + { + FHyperTwistSpeechUsageCostDashboardIssue Issue; + Issue.IssueId = TEXT("provider-hard-blocked"); + Issue.IssueKind = TEXT("hard-blocked"); + Issue.StatusLine = TEXT("Provider usage blocked."); + Issue.DetailLine = FString::Printf( + TEXT("The quota state is '%s'. Resolve provider budget posture before depending on further provider-backed capture."), + *EffectiveQuotaState.StateReason + ); + Issue.RecommendedActionId = Profile.BudgetInspectActionId; + AddSpeechUsageCostDashboardIssue(ShellState, Issue); + ShellState.ActiveIssue = Issue; + ShellState.StatusLine = Issue.StatusLine; + ShellState.DetailLine = Issue.DetailLine; + ShellState.bEscalationBannerVisible = Profile.bSupportsQuotaEscalationBanner; + } + else if (ShellState.bRateLimited) + { + FHyperTwistSpeechUsageCostDashboardIssue Issue; + Issue.IssueId = TEXT("provider-rate-limited"); + Issue.IssueKind = TEXT("rate-limited"); + Issue.StatusLine = TEXT("Provider rate limit reached."); + Issue.DetailLine = FString::Printf( + TEXT("Remaining requests: %d. Remaining audio seconds: %d. Review provider budget posture before more routed capture."), + ShellState.RemainingRequestCount, + ShellState.RemainingAudioSeconds + ); + Issue.RecommendedActionId = Profile.BudgetInspectActionId; + AddSpeechUsageCostDashboardIssue(ShellState, Issue); + ShellState.ActiveIssue = Issue; + ShellState.StatusLine = Issue.StatusLine; + ShellState.DetailLine = Issue.DetailLine; + ShellState.bEscalationBannerVisible = Profile.bSupportsQuotaEscalationBanner; + } + else if (!ShellState.bQuotaKnown + && ShellState.bRouteReady + && EffectiveError.IsEmpty() + && ServiceHealth.bReady) + { + FHyperTwistSpeechUsageCostDashboardIssue Issue; + Issue.IssueId = TEXT("provider-budget-posture-unknown"); + Issue.IssueKind = TEXT("budget-posture-unknown"); + Issue.StatusLine = TEXT("Provider budget posture unknown."); + Issue.DetailLine = + TEXT("Quota and spend snapshots are not confirmed yet. Treat the current dashboard values as routing diagnostics, not settled provider billing."); + Issue.RecommendedActionId = Profile.BudgetInspectActionId; + AddSpeechUsageCostDashboardIssue(ShellState, Issue); + ShellState.ActiveIssue = Issue; + ShellState.bEscalationBannerVisible = Profile.bSupportsQuotaEscalationBanner; + } + + if (ShellState.bEstimateBadgeVisible && !ShellState.bProviderReceiptPosted && ShellState.Issues.Num() <= 0) + { + ShellState.DetailLine += TEXT(" Current cost remains estimate-only."); + } + + ShellState.ActiveIssueCount = ShellState.Issues.Num(); + } + + void SynchronizeSpeechUsageCostDashboardState( + FHyperTwistTrainingCompanionSpeechSessionState& SessionState + ) + { + FHyperTwistSpeechSessionConfig EffectiveConfig = SessionState.SessionConfig; + ApplySpeechUsageCostDefaults(EffectiveConfig); + if (!SessionState.UsageCostDashboardShellState.IsStructurallyValid()) + { + SessionState.UsageCostDashboardShellState = + BuildSpeechUsageCostDashboardShellState(EffectiveConfig, &SessionState); + } + + const FHyperTwistSpeechUsageCostDashboardShellProfile Profile = + EffectiveConfig.UsageCostDashboardShellProfileDefinition.IsStructurallyValid() + ? EffectiveConfig.UsageCostDashboardShellProfileDefinition + : BuildDefaultSpeechUsageCostDashboardShellProfile( + !EffectiveConfig.UsageCostDashboardShellProfileId.IsEmpty() + ? EffectiveConfig.UsageCostDashboardShellProfileId + : TEXT("speech-provider-usage-cost-dashboard-shell-v1") + ); + + FHyperTwistSpeechUsageCostDashboardShellState& ShellState = + SessionState.UsageCostDashboardShellState; + ShellState.DashboardShellProfileId = Profile.DashboardShellProfileId; + ShellState.AvailableActionIds.Reset(); + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings) + { + ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId); + } + + ApplySpeechUsageCostDashboardPosture( + Profile, + EffectiveConfig, + SessionState.ServiceHealth, + SessionState.LastError, + ShellState + ); + + ShellState.AvailableActionIds.AddUnique(Profile.RefreshActionId); + ShellState.AvailableActionIds.AddUnique(Profile.RouteInspectActionId); + ShellState.AvailableActionIds.AddUnique(Profile.BudgetInspectActionId); + if (ShellState.ActiveIssue.IsStructurallyValid()) + { + ShellState.AvailableActionIds.AddUnique(ShellState.ActiveIssue.RecommendedActionId); + } + } + FHyperTwistSpeechMicrophoneShellState BuildCoachCommandMicrophoneShellState( const FHyperTwistSpeechSessionConfig& SessionConfig, const FHyperTwistTrainingCompanionSpeechSessionState* PriorState, @@ -4344,6 +4707,58 @@ namespace HyperTwistTrainingSubsystemInternal return ShellState; } + FHyperTwistSpeechUsageCostDashboardShellState BuildSpeechUsageCostDashboardShellState( + const FHyperTwistSpeechSessionConfig& SessionConfig, + const FHyperTwistTrainingCompanionSpeechSessionState* PriorState + ) + { + const FHyperTwistSpeechUsageCostDashboardShellProfile Profile = + SessionConfig.UsageCostDashboardShellProfileDefinition.IsStructurallyValid() + ? SessionConfig.UsageCostDashboardShellProfileDefinition + : BuildDefaultSpeechUsageCostDashboardShellProfile( + !SessionConfig.UsageCostDashboardShellProfileId.IsEmpty() + ? SessionConfig.UsageCostDashboardShellProfileId + : TEXT("speech-provider-usage-cost-dashboard-shell-v1") + ); + + FHyperTwistSpeechUsageCostDashboardShellState ShellState; + ShellState.DashboardShellProfileId = Profile.DashboardShellProfileId; + ShellState.ActiveProviderProfileId = SessionConfig.ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderProfileDefinition.ProviderProfileId + : TEXT("speech-provider/local-http-sidecar-profile-v1"); + ShellState.ActiveProviderDisplayLabel = SessionConfig.ProviderProfileDefinition.IsStructurallyValid() + ? SessionConfig.ProviderProfileDefinition.DisplayLabel + : TEXT("Local HTTP Speech Sidecar"); + ShellState.ActiveServiceLaneId = !SessionConfig.OrchestrationProfile.ServiceLaneId.IsEmpty() + ? SessionConfig.OrchestrationProfile.ServiceLaneId + : TEXT("speech/python-service"); + ShellState.ActiveRouteStateId = SessionConfig.ProviderRouteDecision.IsStructurallyValid() + ? SessionConfig.ProviderRouteDecision.RouteStateId + : TEXT("route-ready"); + ShellState.UsageAggregationWindowId = SessionConfig.UsageEventTemplate.AggregationWindowId; + ShellState.CostAggregationWindowId = SessionConfig.CostEventTemplate.AggregationWindowId; + ShellState.UsageMeterKind = SessionConfig.UsageEventTemplate.MeterKind; + ShellState.CurrencyCode = SessionConfig.CostEventTemplate.CurrencyCode; + ShellState.LatestUsageEventId = SessionConfig.UsageEventTemplate.UsageEventId; + ShellState.LatestCostEventId = SessionConfig.CostEventTemplate.CostEventId; + ShellState.LatestQuotaStateId = TEXT("speech-quota-state/uninitialized"); + ShellState.LatestRouteDecisionId = SessionConfig.ProviderRouteDecision.IsStructurallyValid() + ? SessionConfig.ProviderRouteDecision.RouteDecisionId + : TEXT("speech-route-decision/uninitialized"); + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : Profile.ActionBindings) + { + ShellState.AvailableActionIds.AddUnique(ActionBinding.ActionId); + } + + if (PriorState != nullptr && PriorState->UsageCostDashboardShellState.IsStructurallyValid()) + { + ShellState = PriorState->UsageCostDashboardShellState; + ShellState.DashboardShellProfileId = Profile.DashboardShellProfileId; + } + + return ShellState; + } + FHyperTwistVisionCorrectionState BuildClassicCubeCorrectionState( const FHyperTwistVisionSessionConfig& SessionConfig, const FHyperTwistVisionReconstructionSession& ReconstructionSession, @@ -9779,6 +10194,9 @@ FHyperTwistSpeechTranscriptResult UHyperTwistTrainingSubsystem::SubmitActiveComp HyperTwistTrainingSubsystemInternal::SynchronizeSpeechMicrophoneShellState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState( + ActiveCompanionSpeechSessionState + ); return Result; } @@ -10693,6 +11111,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() HyperTwistTrainingSubsystemInternal::SynchronizeSpeechMicrophoneShellState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState( + ActiveCompanionSpeechSessionState + ); return; } @@ -10718,6 +11139,9 @@ void UHyperTwistTrainingSubsystem::RefreshCompanionSpeechServiceHealth() HyperTwistTrainingSubsystemInternal::SynchronizeSpeechMicrophoneShellState( ActiveCompanionSpeechSessionState ); + HyperTwistTrainingSubsystemInternal::SynchronizeSpeechUsageCostDashboardState( + ActiveCompanionSpeechSessionState + ); } void UHyperTwistTrainingSubsystem::AppendRecognitionReplayEvent( diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h index e2be811..391dc5b 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistRecognition/HyperTwistRecognitionTypes.h @@ -2575,6 +2575,300 @@ struct FHyperTwistSpeechQuotaRateLimitState } }; +USTRUCT(BlueprintType) +struct FHyperTwistSpeechUsageCostDashboardShellProfile +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DashboardShellProfileId = TEXT("speech-provider-usage-cost-dashboard-shell-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ShellKind = TEXT("operator-usage-cost-dashboard"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UsageSurfaceMode = TEXT("normalized-usage-summary-card"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CostSurfaceMode = TEXT("estimate-first-cost-card"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString EscalationBannerMode = TEXT("route-and-budget-posture-banner"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RefreshActionId = TEXT("refresh-provider-usage-cost-dashboard"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RouteInspectActionId = TEXT("inspect-provider-route"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString BudgetInspectActionId = TEXT("review-provider-budget"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsProviderSummary = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsEstimateBadges = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsQuotaEscalationBanner = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bSupportsRouteDiagnostics = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Panels; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray ActionBindings; + + bool IsStructurallyValid() const + { + if (DashboardShellProfileId.IsEmpty() + || ShellKind.IsEmpty() + || UsageSurfaceMode.IsEmpty() + || CostSurfaceMode.IsEmpty() + || EscalationBannerMode.IsEmpty() + || RefreshActionId.IsEmpty() + || RouteInspectActionId.IsEmpty() + || BudgetInspectActionId.IsEmpty() + || Panels.Num() <= 0 + || ActionBindings.Num() <= 0) + { + return false; + } + + for (const FHyperTwistSpeechShellPanelLayout& Panel : Panels) + { + if (!Panel.IsStructurallyValid()) + { + return false; + } + } + + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : ActionBindings) + { + if (!ActionBinding.IsStructurallyValid()) + { + return false; + } + } + + return true; + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechUsageCostDashboardIssue +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString IssueId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString IssueKind; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString RecommendedActionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRequiresOperatorAttention = true; + + bool IsStructurallyValid() const + { + return !IssueId.IsEmpty() + && !IssueKind.IsEmpty() + && !StatusLine.IsEmpty() + && !DetailLine.IsEmpty() + && !RecommendedActionId.IsEmpty(); + } +}; + +USTRUCT(BlueprintType) +struct FHyperTwistSpeechUsageCostDashboardShellState +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DashboardShellProfileId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveProviderProfileId = TEXT("speech-provider/local-http-sidecar-profile-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveProviderDisplayLabel = TEXT("Local HTTP Speech Sidecar"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveServiceLaneId = TEXT("speech/python-service"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString ActiveRouteStateId = TEXT("route-ready"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UsageAggregationWindowId = TEXT("service-health-window"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CostAggregationWindowId = TEXT("service-health-window"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UsageMeterKind = TEXT("audio-seconds"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString CurrencyCode = TEXT("USD"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString StatusLine = TEXT("Provider usage and budget posture within current bounds."); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString DetailLine = TEXT("Normalized usage, cost, and quota posture are ready for operator review."); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestUsageEventId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestCostEventId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestQuotaStateId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString LatestRouteDecisionId; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float DisplayedUsageQuantity = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float DisplayedEstimatedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float DisplayedReportedCostUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + float RemainingEstimatedSpendUsd = 0.0f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RemainingRequestCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 RemainingAudioSeconds = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bDashboardVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bProviderSummaryVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bUsageCardVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bCostCardVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bQuotaCardVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRouteDiagnosticsVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bEstimateBadgeVisible = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bEscalationBannerVisible = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bProviderReceiptPosted = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bQuotaKnown = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRateLimited = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bHardBlocked = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + bool bRouteReady = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + int32 ActiveIssueCount = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechUsageCostDashboardIssue ActiveIssue; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray Issues; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + TArray AvailableActionIds; + + bool IsStructurallyValid() const + { + if (DashboardShellProfileId.IsEmpty() + || ActiveProviderProfileId.IsEmpty() + || ActiveProviderDisplayLabel.IsEmpty() + || ActiveServiceLaneId.IsEmpty() + || ActiveRouteStateId.IsEmpty() + || UsageAggregationWindowId.IsEmpty() + || CostAggregationWindowId.IsEmpty() + || UsageMeterKind.IsEmpty() + || CurrencyCode.IsEmpty() + || StatusLine.IsEmpty() + || DetailLine.IsEmpty() + || LatestUsageEventId.IsEmpty() + || LatestCostEventId.IsEmpty() + || LatestQuotaStateId.IsEmpty() + || LatestRouteDecisionId.IsEmpty() + || DisplayedUsageQuantity < 0.0f + || DisplayedEstimatedCostUsd < 0.0f + || DisplayedReportedCostUsd < 0.0f + || RemainingEstimatedSpendUsd < 0.0f + || RemainingRequestCount < 0 + || RemainingAudioSeconds < 0 + || ActiveIssueCount < 0 + || AvailableActionIds.Num() <= 0) + { + return false; + } + + if (ActiveIssueCount != Issues.Num()) + { + return false; + } + + if (ActiveIssueCount > 0 && !ActiveIssue.IsStructurallyValid()) + { + return false; + } + + for (const FHyperTwistSpeechUsageCostDashboardIssue& Issue : Issues) + { + if (!Issue.IsStructurallyValid()) + { + return false; + } + } + + for (const FString& ActionId : AvailableActionIds) + { + if (ActionId.IsEmpty()) + { + return false; + } + } + + return true; + } +}; + USTRUCT(BlueprintType) struct FHyperTwistSpeechSessionConfig { @@ -2646,6 +2940,12 @@ struct FHyperTwistSpeechSessionConfig UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechNormalizedCostEvent CostEventTemplate; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FString UsageCostDashboardShellProfileId = TEXT("speech-provider-usage-cost-dashboard-shell-v1"); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechUsageCostDashboardShellProfile UsageCostDashboardShellProfileDefinition; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FString PayloadCustodyProfileId = TEXT("downloadable-model-payload-custody-v1"); @@ -2692,6 +2992,8 @@ struct FHyperTwistSpeechSessionConfig || !UsageCostAccountingProfileDefinition.IsStructurallyValid() || !UsageEventTemplate.IsStructurallyValid() || !CostEventTemplate.IsStructurallyValid() + || UsageCostDashboardShellProfileId.IsEmpty() + || !UsageCostDashboardShellProfileDefinition.IsStructurallyValid() || PayloadCustodyProfileId.IsEmpty() || !PayloadCustodyProfileDefinition.IsStructurallyValid() || RequiredModelPayloads.Num() <= 0 diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h index 88842e1..9fdac99 100644 --- a/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Public/HyperTwistTraining/HyperTwistTrainingTypes.h @@ -1940,6 +1940,9 @@ struct FHyperTwistTrainingCompanionSpeechSessionState UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechMicrophoneShellState MicrophoneShellState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") + FHyperTwistSpeechUsageCostDashboardShellState UsageCostDashboardShellState; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist") FHyperTwistSpeechTranscriptResult LastTranscriptResult; }; diff --git a/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAAUsageCostDashboardShellContractTest.cpp b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAAUsageCostDashboardShellContractTest.cpp new file mode 100644 index 0000000..0e6f128 --- /dev/null +++ b/UnrealHyperTwist/Source/UnrealHyperTwist/Tests/HyperTwistWhisperCppPhase6RAAUsageCostDashboardShellContractTest.cpp @@ -0,0 +1,267 @@ +// Copyright HyperTwist, Inc. All Rights Reserved. + +#include "Misc/AutomationTest.h" + +#include "Engine/GameInstance.h" +#include "HyperTwistRecognition/HyperTwistSpeechClient.h" +#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h" +#include "UObject/UnrealType.h" + +#if WITH_AUTOMATION_TESTS + +namespace HyperTwistWhisperCppPhase6RAATestInternal +{ + FHyperTwistTrainingDeck MakeSpeechDeck() + { + FHyperTwistTrainingDeck Deck; + Deck.DeckId = TEXT("phase6r-aa/provider-usage-cost-dashboard"); + Deck.Title = TEXT("Phase 6R-AA Provider Usage/Cost Dashboard"); + Deck.DeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + + FHyperTwistTrainingCase TrainingCase; + TrainingCase.CaseId = TEXT("phase6r-aa-case"); + TrainingCase.PuzzleId = TEXT("cube/3x3x3"); + TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence; + TrainingCase.PromptLabel = TEXT("Phase 6R-AA Coach Speech"); + TrainingCase.AllowedDeliveryModes = { + EHyperTwistTrainingDeliveryMode::CoachReviewed + }; + Deck.Cases = {TrainingCase}; + return Deck; + } + + void ForceSpeechClientKind(UHyperTwistTrainingSubsystem* TrainingSubsystem, const FString& ClientKind) + { + if (TrainingSubsystem == nullptr) + { + return; + } + + if (FStrProperty* SpeechClientKindProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("CompanionSpeechClientKind") + )) + { + SpeechClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, ClientKind); + } + } + + UHyperTwistTrainingSubsystem* MakeSpeechSubsystem(const FString& SessionId, const FString& ClientKind) + { + UGameInstance* GameInstance = NewObject(GetTransientPackage()); + if (GameInstance == nullptr) + { + return nullptr; + } + + UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject(GameInstance); + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + ForceSpeechClientKind(TrainingSubsystem, ClientKind); + const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck( + MakeSpeechDeck(), + TEXT("phase6r-aa-user"), + SessionId, + EHyperTwistTrainingDeliveryMode::CoachReviewed + ); + return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr; + } + + UHyperTwistHttpSpeechClient* ResolveHttpSpeechClient(UHyperTwistTrainingSubsystem* TrainingSubsystem) + { + if (TrainingSubsystem == nullptr) + { + return nullptr; + } + + FString CloseError; + TrainingSubsystem->CloseActiveCompanionSpeechSession(CloseError); + + if (FObjectPropertyBase* ClientProperty = FindFProperty( + UHyperTwistTrainingSubsystem::StaticClass(), + TEXT("ActiveCompanionSpeechClientObject") + )) + { + return Cast( + ClientProperty->GetObjectPropertyValue_InContainer(TrainingSubsystem) + ); + } + + return nullptr; + } + + bool ContainsPanelId( + const TArray& Panels, + const FString& ExpectedPanelId + ) + { + for (const FHyperTwistSpeechShellPanelLayout& Panel : Panels) + { + if (Panel.PanelId == ExpectedPanelId) + { + return true; + } + } + + return false; + } + + bool ContainsActionId( + const TArray& ActionBindings, + const FString& ExpectedActionId + ) + { + for (const FHyperTwistSpeechShellActionBinding& ActionBinding : ActionBindings) + { + if (ActionBinding.ActionId == ExpectedActionId) + { + return true; + } + } + + return false; + } +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAAUsageCostDashboardSessionConfigTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AA.UsageCostDashboardSessionConfig", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAAUsageCostDashboardSessionConfigTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAATestInternal::MakeSpeechSubsystem( + TEXT("phase6r-aa-config-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AA."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock dashboard route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock dashboard speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistSpeechSessionConfig& Config = + TrainingSubsystem->GetActiveCompanionSpeechSessionState().SessionConfig; + + TestTrue(TEXT("The dashboard session config must remain structurally valid."), Config.IsStructurallyValid()); + TestEqual(TEXT("The dashboard shell profile id must be present."), Config.UsageCostDashboardShellProfileId, TEXT("speech-provider-usage-cost-dashboard-shell-v1")); + TestTrue(TEXT("The dashboard shell profile must remain structurally valid."), Config.UsageCostDashboardShellProfileDefinition.IsStructurallyValid()); + TestEqual(TEXT("The dashboard shell must preserve the operator-facing shell kind."), Config.UsageCostDashboardShellProfileDefinition.ShellKind, TEXT("operator-usage-cost-dashboard")); + TestEqual(TEXT("The dashboard shell must preserve the refresh action id."), Config.UsageCostDashboardShellProfileDefinition.RefreshActionId, TEXT("refresh-provider-usage-cost-dashboard")); + TestEqual(TEXT("The dashboard shell must preserve the route inspect action id."), Config.UsageCostDashboardShellProfileDefinition.RouteInspectActionId, TEXT("inspect-provider-route")); + TestTrue(TEXT("The dashboard shell must support quota escalation banners."), Config.UsageCostDashboardShellProfileDefinition.bSupportsQuotaEscalationBanner); + TestTrue(TEXT("The dashboard shell must include the provider summary panel."), HyperTwistWhisperCppPhase6RAATestInternal::ContainsPanelId(Config.UsageCostDashboardShellProfileDefinition.Panels, TEXT("provider-summary-card"))); + TestTrue(TEXT("The dashboard shell must include the route diagnostics action."), HyperTwistWhisperCppPhase6RAATestInternal::ContainsActionId(Config.UsageCostDashboardShellProfileDefinition.ActionBindings, TEXT("inspect-provider-route"))); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAAUsageCostDashboardShellStateTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AA.UsageCostDashboardShellState", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAAUsageCostDashboardShellStateTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAATestInternal::MakeSpeechSubsystem( + TEXT("phase6r-aa-shell-session"), + TEXT("mock") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AA."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + FString OpenError; + TestTrue(TEXT("The mock dashboard route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestTrue(TEXT("Opening the mock dashboard speech session must not report an error."), OpenError.IsEmpty()); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechServiceHealth& Health = SessionState.ServiceHealth; + const FHyperTwistSpeechUsageCostDashboardShellState& Dashboard = + SessionState.UsageCostDashboardShellState; + + TestTrue(TEXT("The service health must expose the usage/cost dashboard shell."), Health.Capabilities.Contains(TEXT("providerUsageCostDashboardShell"))); + TestTrue(TEXT("The service health must expose the quota escalation banner capability."), Health.Capabilities.Contains(TEXT("providerQuotaEscalationBanner"))); + TestTrue(TEXT("The service health must expose the route cost diagnostics capability."), Health.Capabilities.Contains(TEXT("providerRouteCostDiagnostics"))); + + TestTrue(TEXT("The dashboard shell state must remain structurally valid."), Dashboard.IsStructurallyValid()); + TestEqual(TEXT("The dashboard shell must preserve the mock provider profile."), Dashboard.ActiveProviderProfileId, TEXT("speech-provider/mock-offline-profile-v1")); + TestEqual(TEXT("The dashboard shell must preserve the ready route state."), Dashboard.ActiveRouteStateId, TEXT("route-ready")); + TestTrue(TEXT("The dashboard shell must preserve estimate badge visibility."), Dashboard.bEstimateBadgeVisible); + TestTrue(TEXT("The dashboard shell must preserve quota visibility."), Dashboard.bQuotaKnown); + TestFalse(TEXT("The dashboard shell must not escalate healthy mock posture."), Dashboard.bEscalationBannerVisible); + TestEqual(TEXT("The dashboard shell must preserve zero active issues on the healthy mock route."), Dashboard.ActiveIssueCount, 0); + TestTrue(TEXT("The dashboard shell must preserve the refresh action."), Dashboard.AvailableActionIds.Contains(TEXT("refresh-provider-usage-cost-dashboard"))); + TestTrue(TEXT("The dashboard shell must preserve the budget review action."), Dashboard.AvailableActionIds.Contains(TEXT("review-provider-budget"))); + TestTrue(TEXT("The dashboard shell must surface the mock usage quantity."), FMath::IsNearlyEqual(Dashboard.DisplayedUsageQuantity, 0.75f)); + TestTrue(TEXT("The dashboard shell must surface the mock estimated cost."), FMath::IsNearlyEqual(Dashboard.DisplayedEstimatedCostUsd, 0.01f)); + return true; +} + +IMPLEMENT_SIMPLE_AUTOMATION_TEST( + FHyperTwistWhisperCppPhase6RAAProviderBackedDashboardFallbackTest, + "HyperTwist.Permissive.WhisperCpp.Phase6R.AA.ProviderBackedDashboardFallback", + EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter +) + +bool FHyperTwistWhisperCppPhase6RAAProviderBackedDashboardFallbackTest::RunTest(const FString& Parameters) +{ + UHyperTwistTrainingSubsystem* TrainingSubsystem = + HyperTwistWhisperCppPhase6RAATestInternal::MakeSpeechSubsystem( + TEXT("phase6r-aa-provider-session"), + TEXT("http-sidecar") + ); + TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AA."), TrainingSubsystem); + if (TrainingSubsystem == nullptr) + { + return false; + } + + UHyperTwistHttpSpeechClient* HttpSpeechClient = + HyperTwistWhisperCppPhase6RAATestInternal::ResolveHttpSpeechClient(TrainingSubsystem); + TestNotNull(TEXT("The provider-backed speech client must be available for the dashboard fallback test."), HttpSpeechClient); + if (HttpSpeechClient == nullptr) + { + return false; + } + + HttpSpeechClient->ServiceBaseUrl.Reset(); + + FString OpenError; + TestFalse(TEXT("The provider-backed dashboard path must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)); + TestEqual(TEXT("The provider-backed dashboard path must report the missing service base URL."), OpenError, TEXT("service-base-url-missing")); + + const FHyperTwistTrainingCompanionSpeechSessionState SessionState = + TrainingSubsystem->GetActiveCompanionSpeechSessionState(); + const FHyperTwistSpeechUsageCostDashboardShellState& Dashboard = + SessionState.UsageCostDashboardShellState; + + TestTrue(TEXT("The provider-backed dashboard fallback must remain structurally valid."), Dashboard.IsStructurallyValid()); + TestEqual(TEXT("The fallback dashboard must preserve the sidecar profile id."), Dashboard.ActiveProviderProfileId, TEXT("speech-provider/local-http-sidecar-profile-v1")); + TestEqual(TEXT("The fallback dashboard must preserve the awaiting-endpoint route state."), Dashboard.ActiveRouteStateId, TEXT("route-awaiting-endpoint")); + TestFalse(TEXT("The fallback dashboard must expose an unready route."), Dashboard.bRouteReady); + TestFalse(TEXT("The fallback dashboard must keep quota unknown during route failure."), Dashboard.bQuotaKnown); + TestTrue(TEXT("The fallback dashboard must escalate route failure posture."), Dashboard.bEscalationBannerVisible); + TestEqual(TEXT("The fallback dashboard must surface one active issue."), Dashboard.ActiveIssueCount, 1); + TestEqual(TEXT("The fallback dashboard must surface the route-degraded issue kind."), Dashboard.ActiveIssue.IssueKind, TEXT("route-degraded")); + TestEqual(TEXT("The fallback dashboard must preserve the route inspect action."), Dashboard.ActiveIssue.RecommendedActionId, TEXT("inspect-provider-route")); + TestTrue(TEXT("The fallback dashboard detail line must preserve the missing-endpoint reason."), Dashboard.DetailLine.Contains(TEXT("service-base-url-missing"))); + return true; +} + +#endif diff --git a/docs/REPO_LICENSE_TRACKING.md b/docs/REPO_LICENSE_TRACKING.md index 2156288..f905cbc 100644 --- a/docs/REPO_LICENSE_TRACKING.md +++ b/docs/REPO_LICENSE_TRACKING.md @@ -191,8 +191,12 @@ Status update on `2026-05-21`: pass is now consumed - the bounded first-party `Phase 6R-Z` normalized usage/cost event-model packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-AA` first-party operator-facing - provider usage/cost dashboard shell preparation/control pass, not a new restrictive packet by +- the generic source-backed `Phase 6R-AA` first-party operator-facing provider usage/cost + dashboard shell control pass is now consumed +- the bounded first-party `Phase 6R-AA` operator-facing provider usage/cost dashboard shell + packet is now landed in current code +- the current next bounded move is a source-backed `Phase 6R-AB` first-party provider + usage/cost history/export shell preparation/control pass, not a new restrictive packet by default - use the repo-row README census, the portfolio standing refresh backfill, and the `2R-A` ownership contract for the current queue after that correction @@ -342,8 +346,8 @@ Current practical interpretation: - the landed `PostHog/posthog` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_D_POSTHOG_CONTROL_PLANE_IMPLEMENTATION_2026-05-13.md` - the landed `screenpipe/screenpipe` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_E_SCREENPIPE_CAPTURE_HISTORY_IMPLEMENTATION_2026-05-13.md` - the landed `remotion-dev/remotion` boundary-sensitive widening packet now lives in `docs/HYPERTWIST_PHASE_4R_PACKET_4R_F_REMOTION_MEDIA_EXPORT_IMPLEMENTATION_2026-05-13.md` -- the next bounded move is a source-backed `Phase 6R-Z` first-party normalized usage/cost - event-model preparation/control pass +- the next bounded move is a source-backed `Phase 6R-AB` first-party provider usage/cost + history/export shell preparation/control pass Companion docs: diff --git a/docs/arch/HYPERTWIST_PHASE6R_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_IMPLEMENTATION_PACKET_2026-05-25.md b/docs/arch/HYPERTWIST_PHASE6R_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_IMPLEMENTATION_PACKET_2026-05-25.md new file mode 100644 index 0000000..ae7f444 --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_IMPLEMENTATION_PACKET_2026-05-25.md @@ -0,0 +1,159 @@ +# HyperTwist Phase 6R-AA first-party operator-facing provider usage/cost dashboard shell implementation packet + +Created on `2026-05-25` + +## Status + +- first-party HyperTwist packet +- bounded `Phase 6R-AA` implementation slice + +## Purpose + +This packet lands the next bounded first-party slice above the landed speech +session, microphone-shell, payload-custody, provider-custody, +provider-routing, and normalized usage/cost event seams. + +The landed slice is: + +- first-party operator-facing provider usage/cost dashboard shell + +It is not: + +- an actual billing export packet +- an actual charge-settlement packet +- a usage/cost history export packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership packet +- a provider-specific overlay packet +- a broad assistant-platform packet + +## Current authority basis + +This implementation packet stands on: + +- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md` +- `docs/arch/HYPERTWIST_PHASE6R_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_PREPARATION_PACKET_2026-05-25.md` +- `docs/arch/HYPERTWIST_PHASE6R_Z_FIRST_PARTY_NORMALIZED_USAGE_COST_EVENT_MODEL_IMPLEMENTATION_PACKET_2026-05-25.md` + +The preserved owners do not change: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the usage/cost governance owner +- `ggml-org/whisper.cpp` remains bounded to offline STT donor seams +- `SYSTRAN/faster-whisper` remains bounded to complementary Python + orchestration/service-lane seams + +## Landed scope + +The current code now owns a bounded operator-facing dashboard seam through: + +- retained recognition contract types for: + - `FHyperTwistSpeechUsageCostDashboardShellProfile` + - `FHyperTwistSpeechUsageCostDashboardIssue` + - `FHyperTwistSpeechUsageCostDashboardShellState` + - expanded `FHyperTwistSpeechSessionConfig` + - expanded `FHyperTwistTrainingCompanionSpeechSessionState` +- contract-library defaults for: + - dashboard shell profile ids and definitions + - dashboard panel ids and action bindings + - service-health capability exposure +- active companion session-config and service-health dashboard composition in: + - `UHyperTwistTrainingSubsystem` +- direct speech-client health capability exposure in: + - `UHyperTwistHttpSpeechClient` + - `UHyperTwistMockSpeechClient` +- focused automation coverage in: + - `HyperTwistWhisperCppPhase6RAAUsageCostDashboardShellContractTest.cpp` + +## Why this is still intentionally bounded + +This packet lands the operator-facing dashboard shell family only. + +Still deferred: + +- provider usage/cost history/export shell surfaces +- actual billing export or settlement +- actual downloadable model/payload shipping +- real device-permission workflow +- native audio-device route ownership beyond bounded shell posture +- broad assistant-platform scope + +## Validation + +Build validation: + +- `UnrealHyperTwistEditor Win64 Development` + +Focused automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.AA` `3/3` + +Regression automation validation: + +- `HyperTwist.Permissive.WhisperCpp.Phase6R.Z` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.Y` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.X` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.W` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.V` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.U` `3/3` +- `HyperTwist.Permissive.WhisperCpp.Phase6R.E` `3/3` +- `HyperTwist.Permissive.FasterWhisper.Phase6R.F` `3/3` +- `HyperTwist.Permissive.Piper.Phase6R.G` `3/3` +- `HyperTwist.Permissive.Coqui.Phase6R.H` `3/3` +- `HyperTwist.CleanRoom.CubeDesk` `14/14` + +Non-blocking warnings stayed limited to the existing Unreal headless/editor +noise, the pre-existing plugin dependency warning on `UnrealMCP`, and the +known `http:/speech/health` and `http:/vision/health` hostname-resolution +warnings in provider-backed automation. + +## Queue effect + +This packet consumes the current `Phase 6R-AA` implementation slice. + +`ggml-org/whisper.cpp` remains only partially incorporated: + +- landed now: + - speech transcript session boundary + - live microphone shell boundary + - device-permission and capture-route readiness shell posture + - downloadable model and payload custody boundary + - first-party provider-profile and BYOK custody boundary + - first-party provider routing and workflow-policy boundary + - first-party normalized usage/cost event-model boundary + - first-party operator-facing provider usage/cost dashboard shell boundary + for: + - dashboard shell profile ids and definitions + - dashboard issue and shell-state posture + - companion-session dashboard composition + - capability exposure for: + - `providerUsageCostDashboardShell` + - `providerQuotaEscalationBanner` + - `providerRouteCostDiagnostics` +- still deferred: + - provider usage/cost history/export shell surfaces + - actual downloadable model/payload shipping + - actual billing export or settlement + - real device-permission workflow + - native audio-device route ownership beyond bounded shell posture + - broad assistant-platform scope + +The next clean move is: + +- a source-backed `Phase 6R-AB` first-party provider usage/cost history/export + shell preparation/control pass + +Keep the future sequencing guards visible: + +- first-party HyperTwist + - keep the top-level provider/session contract, routing/policy lane, + usage/cost governance lane, and operator-facing dashboard shell lane + first-party +- `ggml-org/whisper.cpp` + - keep code-license judgments separate from actual model or payload shipping + review in any follow-on packet +- `SYSTRAN/faster-whisper` + - keep the row complementary to the landed provider/session, custody, + routing, usage/cost, and dashboard seams rather than widening it into + usage/cost history/export ownership diff --git a/docs/arch/HYPERTWIST_PHASE6R_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_PREPARATION_PACKET_2026-05-25.md b/docs/arch/HYPERTWIST_PHASE6R_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_PREPARATION_PACKET_2026-05-25.md new file mode 100644 index 0000000..157bdaa --- /dev/null +++ b/docs/arch/HYPERTWIST_PHASE6R_AA_FIRST_PARTY_OPERATOR_FACING_PROVIDER_USAGE_COST_DASHBOARD_SHELL_PREPARATION_PACKET_2026-05-25.md @@ -0,0 +1,127 @@ +# HyperTwist Phase 6R-AA first-party operator-facing provider usage/cost dashboard shell preparation packet + +Created on `2026-05-25` + +## Status + +- first-party HyperTwist packet +- source-backed `Phase 6R-AA` preparation/control slice + +## Purpose + +This packet scopes the next bounded first-party slice above the landed speech +session, microphone-shell, payload-custody, provider-custody, +provider-routing, and normalized usage/cost event seams. + +The granted slice is: + +- first-party operator-facing provider usage/cost dashboard shell only + +It is not: + +- an actual billing or charge-settlement packet +- a usage/cost history export packet +- an actual payload-shipping packet +- a real device-permission workflow packet +- a native audio-device route ownership packet +- a provider-specific overlay packet +- a voice-output packet +- a broad assistant-platform packet + +## Current authority basis + +This control pass stands on: + +- `docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md` +- `docs/arch/HYPERTWIST_PHASE6R_Z_FIRST_PARTY_NORMALIZED_USAGE_COST_EVENT_MODEL_IMPLEMENTATION_PACKET_2026-05-25.md` +- `docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md` + +The preserved owners do not change here: + +- first-party HyperTwist remains the top-level provider/session owner +- first-party HyperTwist remains the usage/cost governance owner +- `ggml-org/whisper.cpp` remains bounded to offline STT donor seams +- `SYSTRAN/faster-whisper` remains bounded to complementary Python + orchestration/service-lane seams + +## Granted family + +The bounded `Phase 6R-AA` slice may land: + +1. first-party operator-facing usage/cost dashboard shell profile ids and + definitions +2. dashboard issue and shell-state record shapes +3. session-config and companion-session-state expansion for those shapes +4. bounded capability exposure for: + - `providerUsageCostDashboardShell` + - `providerQuotaEscalationBanner` + - `providerRouteCostDiagnostics` +5. focused automation for mock healthy posture and provider-backed fallback + posture + +The packet must stay out of: + +- actual billing export, settlement, or invoice reconciliation +- actual payload download or redistribution ownership +- real device-permission workflow ownership +- native audio-device route ownership +- provider-specific dashboard skinning + +## Proposed implementation shape + +Land the narrower first-party boundary through: + +- retained recognition contract types for: + - usage/cost dashboard shell profile + - dashboard issue + - dashboard shell state + - expanded speech session config + - expanded companion speech session state +- contract-library defaults for: + - dashboard shell profile ids and definitions + - dashboard action bindings and panel ids + - service-health capability exposure +- training-subsystem composition for: + - provider summary posture + - route diagnostics posture + - usage and cost summary cards + - quota escalation banner posture + - provider-backed fallback issue posture +- direct speech-client health capability exposure for: + - `providerUsageCostDashboardShell` + - `providerQuotaEscalationBanner` + - `providerRouteCostDiagnostics` + +## Validation target + +Validate with: + +- Unreal build for `UnrealHyperTwistEditor Win64 Development` +- focused automation: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.AA` +- regressions: + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Z` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.Y` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.X` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.W` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.V` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.U` + - `HyperTwist.Permissive.WhisperCpp.Phase6R.E` + - `HyperTwist.Permissive.FasterWhisper.Phase6R.F` + - `HyperTwist.Permissive.Piper.Phase6R.G` + - `HyperTwist.Permissive.Coqui.Phase6R.H` + - `HyperTwist.CleanRoom.CubeDesk` + +## Queue effect + +If this packet lands cleanly, the next bounded move becomes: + +- a source-backed `Phase 6R-AB` first-party provider usage/cost history/export + shell preparation/control pass + +Keep the narrow sequencing guards visible: + +- keep actual billing and settlement separate from dashboard shell ownership +- keep actual payload shipping separate from usage/cost governance +- keep real device-permission workflow and native capture-route ownership + outside this packet diff --git a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md index d5d416c..daa5ec0 100644 --- a/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md +++ b/docs/ops/HYPERTWIST_IMPLEMENTATION_PHASE_1_KICKOFF.md @@ -168,15 +168,19 @@ The next bounded move is now: control pass is now consumed 26. the bounded first-party `Phase 6R-Z` normalized usage/cost event-model packet is now landed in current code -27. the next bounded move is a source-backed `Phase 6R-AA` first-party operator-facing - provider usage/cost dashboard shell preparation/control pass -28. keep the speech-lane guard visible: +27. the generic source-backed `Phase 6R-AA` first-party operator-facing provider usage/cost + dashboard shell control pass is now consumed +28. the bounded first-party `Phase 6R-AA` operator-facing provider usage/cost dashboard shell + packet is now landed in current code +29. the next bounded move is a source-backed `Phase 6R-AB` first-party provider usage/cost + history/export shell preparation/control pass +30. keep the speech-lane guard visible: - keep code-license judgments separate from model, voice, and payload-license review -29. keep the provider-neutral speech-lane guard visible: +31. keep the provider-neutral speech-lane guard visible: - first-party HyperTwist owns the top-level provider/session contract - `whisper.cpp` and `faster-whisper` may win narrower donor slices without inheriting that lane, the provider routing/policy lane, or the usage/cost governance lane -30. keep the `MagicTile` guard visible: +32. keep the `MagicTile` guard visible: - keep broad non-Euclidean interaction or WinForms/OpenTK host-shell ownership closed by default unless a narrower first-party gap is proven above the landed macro-remapping seam diff --git a/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md b/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md index 5789543..1f3473d 100644 --- a/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md +++ b/docs/ops/HYPERTWIST_PROVIDER_NEUTRALITY_AND_BYOK_DOCTRINE_2026-05-21.md @@ -215,8 +215,9 @@ The correct implementation order is: Current landed ordering status: -- steps `1` through `4` now have bounded first-party implementation anchors -- operator-facing overlays remain the next first-party gap +- steps `1` through `5` now have bounded first-party implementation anchors +- usage/cost history/export shells and actual billing/settlement remain later + first-party work - provider-specific adapters remain later work Do not invert that order. diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index f753b80..e4f4097 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -126,6 +126,7 @@ Do not collapse those three tiers into one undifferentiated "features" voice. | First-party provider profile and BYOK custody boundary | Implemented now | landed first-party `Phase 6R-X` | First-party provider-profile definitions, BYOK custody definitions, per-profile model/capability maps, and out-of-band auth-reference wording are now live above the landed speech session, shell, and payload-custody seams. | | First-party provider routing and workflow-policy boundary | Implemented now | landed first-party `Phase 6R-Y` | First-party routing-policy ids/definitions, route-decision summaries, fallback-health posture, and workflow-policy capability exposure are now live above the landed provider-custody seam. | | First-party normalized usage/cost event surface | Implemented now | landed first-party `Phase 6R-Z` | First-party accounting-profile definitions, normalized usage/cost event templates and health summaries, quota/rate-limit state, and bounded capability exposure are now live above the landed provider-routing seam. | +| First-party operator-facing provider usage/cost dashboard shell | Implemented now | landed first-party `Phase 6R-AA` | First-party dashboard shell profiles, dashboard issue/state objects, quota escalation banners, route diagnostics, and operator-facing panel/action contracts are now live above the landed normalized usage/cost seam. | | Analytics/reporting surfaces | Implemented now | landed analytics/reporting packets | Reporting is real, but bounded to accepted retained slices. | | Browser/spatial/media adjunct surfaces | Implemented now | landed `three.js`, `react-three-fiber`, `xr`, `model-viewer`, `remotion` packets | These are implemented bounded families, not proof of unlimited browser-shell parity. | @@ -221,6 +222,7 @@ repo. | Provider-neutral BYOK/profile custody boundary | Implemented now | landed first-party `Phase 6R-X` packet | Current bounded provider-profile ids/definitions, BYOK custody ids/definitions, per-profile model/capability maps, and out-of-band auth-reference wording. | | First-party provider routing and workflow-policy boundary | Implemented now | landed first-party `Phase 6R-Y` packet | Current bounded routing-policy ids/definitions, route-decision summaries, explicit fallback-health posture, and workflow-policy capability exposure above the landed provider/session and provider-custody seams. | | First-party normalized usage/cost event surface | Implemented now | landed first-party `Phase 6R-Z` packet | Current bounded accounting-profile ids/definitions, normalized usage/cost event summaries, quota/rate-limit state, and bounded capability exposure above the landed provider-routing seam. | +| First-party operator-facing provider usage/cost dashboard shell | Implemented now | landed first-party `Phase 6R-AA` packet | Current bounded dashboard shell profile ids/definitions, dashboard issue/state posture, and capability exposure above the landed normalized usage/cost seam. | | OpenAI-compatible custom endpoint support | Deep-source grounded retained | doctrine-defined first-party target | First-class target is now represented in the landed custody model, but full routing/runtime support is still deferred. | | Provider-specific overlays | Shallow placeholder | future provider-family work only | Keep internal until source-grounded and normalized. | diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index 85247f1..358acf5 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -167,10 +167,14 @@ Canonical discovery surfaces for roadmap interpretation: pass is now consumed - the bounded first-party `Phase 6R-Z` normalized usage/cost event-model packet is now landed in current code -- the current next bounded move is a source-backed `Phase 6R-AA` first-party operator-facing - provider usage/cost dashboard shell preparation/control pass, while keeping actual billing, - real device-permission workflow, native capture-route ownership, and actual payload shipping - separately deferred +- the generic source-backed `Phase 6R-AA` first-party operator-facing provider usage/cost + dashboard shell control pass is now consumed +- the bounded first-party `Phase 6R-AA` operator-facing provider usage/cost dashboard shell + packet is now landed in current code +- the current next bounded move is a source-backed `Phase 6R-AB` first-party provider + usage/cost history/export shell preparation/control pass, while keeping actual billing, + settlement, real device-permission workflow, native capture-route ownership, and actual + payload shipping separately deferred - the repo-row implementation queue is now live from that `Phase 6R-A` entry point rather than waiting on another first-party packet @@ -257,16 +261,15 @@ Current routing truth: - `brianpeiris/RiftSketch` - the v6.3 CSV label `not_live_reference_or_discard_candidate` is not the final state by itself; read the closed `0R-E` packet for the retained-versus-discarded split -The next bounded move is a source-backed `Phase 6R-AA` first-party -operator-facing provider usage/cost dashboard shell preparation/control pass. +The next bounded move is a source-backed `Phase 6R-AB` first-party provider +usage/cost history/export shell preparation/control pass. Queue interpretation after that control pass: - then continue with the retained repo-row queue - highest current repo-row queue pressure sits in: - - the first-party operator-facing provider usage/cost dashboard shell - remainder above the - partially landed `whisper.cpp` and `faster-whisper` speech seams + - the first-party provider usage/cost history/export shell remainder above + the partially landed `whisper.cpp` and `faster-whisper` speech seams - `HactarCE/Hyperspeedcube` is now closed for the currently justified retained row: - landed: - puzzle catalog contract @@ -369,8 +372,9 @@ Queue interpretation after that control pass: - normalized usage events - normalized cost events - quota/rate-limit state + - operator-facing provider usage/cost dashboard shell - still deferred: - - operator-facing provider usage/cost dashboard surfaces + - provider usage/cost history/export shell surfaces - real device-permission workflow - native capture-route ownership beyond bounded shell posture - actual downloadable model or payload shipping @@ -418,12 +422,13 @@ Queue interpretation after that control pass: - viseme / gesture runtime integration - broad assistant-platform scope - the next queue shape is now: - - first-party normalized usage/cost event-model assessment above the landed speech session, - shell, payload-custody, provider-custody, and provider-routing seams + - first-party provider usage/cost history/export shell assessment above the landed speech + session, shell, payload-custody, provider-custody, provider-routing, normalized usage/cost, + and operator-facing dashboard seams - the next bounded move should stay narrow: - - a source-backed `Phase 6R-Z` control pass before any widening into actual payload shipping, - real device-permission workflow, broad voice-output ownership, or broad assistant-platform - scope + - a source-backed `Phase 6R-AB` control pass before any widening into actual billing + settlement, actual payload shipping, real device-permission workflow, broad voice-output + ownership, or broad assistant-platform scope - keep the speech-input / voice sidecar legal sequencing guard visible: - keep code-license judgments separate from model, voice, and payload-license review - keep the provider-neutral speech-lane guard visible: