Files
FunnyGirl/Plugins/QwenTTSStreaming/Source/QwenTTSStreamingEditor/Private/QwenTTSPresetDetailsCustomization.cpp
T
2026-05-19 13:42:41 +07:00

287 lines
9.1 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "QwenTTSPresetDetailsCustomization.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "HttpModule.h"
#include "Interfaces/IHttpResponse.h"
#include "Json.h"
#include "PropertyHandle.h"
#include "QwenTTSPresetDataAsset.h"
#include "QwenTTSStreamingLog.h"
#include "QwenTTSStreamingSettingsDataAsset.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Text/STextBlock.h"
#define LOCTEXT_NAMESPACE "FQwenTTSPresetDetailsCustomization"
FText FQwenTTSPresetChoice::GetDisplayText() const
{
if (!Label.IsEmpty())
{
return FText::FromString(FString::Printf(TEXT("%s (%s)"), *Label, *Id));
}
return FText::FromString(Id);
}
TSharedRef<IDetailCustomization> FQwenTTSPresetDetailsCustomization::MakeInstance()
{
return MakeShared<FQwenTTSPresetDetailsCustomization>();
}
void FQwenTTSPresetDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
TArray<TWeakObjectPtr<UObject>> Objects;
DetailBuilder.GetObjectsBeingCustomized(Objects);
if (Objects.Num() > 0)
{
PresetAsset = Cast<UQwenTTSPresetDataAsset>(Objects[0].Get());
}
SettingsHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UQwenTTSPresetDataAsset, Settings));
PresetIdHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UQwenTTSPresetDataAsset, PresetId));
DetailBuilder.HideProperty(SettingsHandle);
DetailBuilder.HideProperty(PresetIdHandle);
IDetailCategoryBuilder& PresetCategory = DetailBuilder.EditCategory(TEXT("Qwen TTS|Preset"));
PresetCategory.AddProperty(SettingsHandle.ToSharedRef());
PresetCategory.AddCustomRow(LOCTEXT("PresetPickerFilter", "Preset Picker"))
.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("PresetPickerLabel", "Preset Picker"))
.Font(IDetailLayoutBuilder::GetDetailFont())
]
.ValueContent()
.MinDesiredWidth(520.0f)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SAssignNew(PresetComboBox, SComboBox<TSharedPtr<FQwenTTSPresetChoice>>)
.OptionsSource(&PresetChoices)
.OnGenerateWidget(this, &FQwenTTSPresetDetailsCustomization::MakePresetComboWidget)
.OnSelectionChanged(this, &FQwenTTSPresetDetailsCustomization::HandleSelectionChanged)
[
SNew(STextBlock)
.Text(this, &FQwenTTSPresetDetailsCustomization::GetSelectedPresetText)
.Font(IDetailLayoutBuilder::GetDetailFont())
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(6.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SButton)
.Text(LOCTEXT("RefreshPresets", "Refresh"))
.OnClicked(this, &FQwenTTSPresetDetailsCustomization::RefreshPresets)
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 4.0f, 0.0f, 0.0f)
[
SNew(STextBlock)
.Text(this, &FQwenTTSPresetDetailsCustomization::GetStatusText)
.Font(IDetailLayoutBuilder::GetDetailFont())
.AutoWrapText(true)
]
];
PresetCategory.AddProperty(PresetIdHandle.ToSharedRef());
RefreshPresets();
}
FReply FQwenTTSPresetDetailsCustomization::RefreshPresets()
{
const FString StatusUrl = GetStatusUrl();
if (StatusUrl.IsEmpty())
{
StatusText = LOCTEXT("MissingStatusUrl", "Set Settings or Gateway Status Url before refreshing presets.");
return FReply::Handled();
}
StatusText = FText::Format(LOCTEXT("LoadingPresets", "Loading presets from {0}"), FText::FromString(StatusUrl));
UE_LOG(LogQwenTTSStreaming, Log, TEXT("Refreshing Qwen TTS presets from %s"), *StatusUrl);
const TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(StatusUrl);
Request->SetVerb(TEXT("GET"));
Request->SetHeader(TEXT("Accept"), TEXT("application/json"));
Request->OnProcessRequestComplete().BindSP(StaticCastSharedRef<FQwenTTSPresetDetailsCustomization>(AsShared()), &FQwenTTSPresetDetailsCustomization::HandlePresetResponse);
Request->ProcessRequest();
return FReply::Handled();
}
void FQwenTTSPresetDetailsCustomization::HandlePresetResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (!bWasSuccessful || !Response.IsValid())
{
StatusText = LOCTEXT("PresetRequestFailed", "Preset request failed.");
UE_LOG(LogQwenTTSStreaming, Warning, TEXT("Qwen TTS preset request failed."));
return;
}
if (Response->GetResponseCode() < 200 || Response->GetResponseCode() >= 300)
{
StatusText = FText::Format(LOCTEXT("PresetHttpFailed", "Preset request failed with HTTP {0}."), Response->GetResponseCode());
UE_LOG(LogQwenTTSStreaming, Warning, TEXT("Qwen TTS preset request failed. HTTP=%d Body=%s"), Response->GetResponseCode(), *Response->GetContentAsString());
return;
}
SetPresetChoicesFromStatusJson(Response->GetContentAsString());
}
void FQwenTTSPresetDetailsCustomization::SetPresetChoicesFromStatusJson(const FString& ResponseText)
{
TSharedPtr<FJsonObject> JsonObject;
const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseText);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
StatusText = LOCTEXT("BadPresetJson", "Preset response is not valid JSON.");
UE_LOG(LogQwenTTSStreaming, Warning, TEXT("Qwen TTS preset response is not valid JSON."));
return;
}
const TArray<TSharedPtr<FJsonValue>>* PresetRefs = nullptr;
if (!JsonObject->TryGetArrayField(TEXT("preset_refs"), PresetRefs))
{
StatusText = LOCTEXT("NoPresetRefs", "Preset response has no preset_refs array.");
UE_LOG(LogQwenTTSStreaming, Warning, TEXT("Qwen TTS preset response has no preset_refs array."));
return;
}
PresetChoices.Reset();
for (const TSharedPtr<FJsonValue>& Value : *PresetRefs)
{
const TSharedPtr<FJsonObject> PresetObject = Value.IsValid() ? Value->AsObject() : nullptr;
if (!PresetObject.IsValid())
{
continue;
}
FString Id;
if (!PresetObject->TryGetStringField(TEXT("id"), Id) || Id.IsEmpty())
{
continue;
}
TSharedRef<FQwenTTSPresetChoice> Choice = MakeShared<FQwenTTSPresetChoice>();
Choice->Id = Id;
PresetObject->TryGetStringField(TEXT("label"), Choice->Label);
PresetObject->TryGetStringField(TEXT("ref_text"), Choice->RefText);
PresetObject->TryGetStringField(TEXT("source"), Choice->Source);
PresetChoices.Add(Choice);
}
if (PresetComboBox.IsValid())
{
PresetComboBox->RefreshOptions();
}
StatusText = FText::Format(LOCTEXT("PresetLoadedCount", "Loaded {0} preset(s)."), PresetChoices.Num());
UE_LOG(LogQwenTTSStreaming, Log, TEXT("Loaded %d Qwen TTS preset(s)."), PresetChoices.Num());
}
FString FQwenTTSPresetDetailsCustomization::GetStatusUrl() const
{
UObject* SettingsObject = nullptr;
if (SettingsHandle.IsValid() && SettingsHandle->GetValue(SettingsObject) == FPropertyAccess::Success)
{
if (const UQwenTTSStreamingSettingsDataAsset* Settings = Cast<UQwenTTSStreamingSettingsDataAsset>(SettingsObject))
{
return Settings->GetResolvedGatewayStatusUrl();
}
}
if (const UQwenTTSPresetDataAsset* Preset = PresetAsset.Get())
{
if (Preset->Settings)
{
return Preset->Settings->GetResolvedGatewayStatusUrl();
}
}
return GetDefault<UQwenTTSStreamingSettingsDataAsset>()->GetResolvedGatewayStatusUrl();
}
TSharedPtr<FQwenTTSPresetChoice> FQwenTTSPresetDetailsCustomization::GetSelectedChoice() const
{
FString CurrentPresetId;
if (PresetIdHandle.IsValid())
{
PresetIdHandle->GetValue(CurrentPresetId);
}
for (const TSharedPtr<FQwenTTSPresetChoice>& Choice : PresetChoices)
{
if (Choice.IsValid() && Choice->Id == CurrentPresetId)
{
return Choice;
}
}
return nullptr;
}
FText FQwenTTSPresetDetailsCustomization::GetSelectedPresetText() const
{
if (const TSharedPtr<FQwenTTSPresetChoice> Choice = GetSelectedChoice())
{
return Choice->GetDisplayText();
}
FString CurrentPresetId;
if (PresetIdHandle.IsValid() && PresetIdHandle->GetValue(CurrentPresetId) == FPropertyAccess::Success && !CurrentPresetId.IsEmpty())
{
return FText::FromString(CurrentPresetId);
}
return LOCTEXT("NoPresetSelected", "Select preset");
}
FText FQwenTTSPresetDetailsCustomization::GetStatusText() const
{
if (const TSharedPtr<FQwenTTSPresetChoice> Choice = GetSelectedChoice())
{
if (!Choice->RefText.IsEmpty())
{
return FText::FromString(FString::Printf(TEXT("%s: %s"), *Choice->Source, *Choice->RefText));
}
}
return StatusText;
}
void FQwenTTSPresetDetailsCustomization::HandleSelectionChanged(TSharedPtr<FQwenTTSPresetChoice> Choice, ESelectInfo::Type SelectInfo)
{
if (!Choice.IsValid() || SelectInfo == ESelectInfo::Direct)
{
return;
}
if (PresetIdHandle.IsValid())
{
PresetIdHandle->SetValue(Choice->Id);
StatusText = FText::Format(LOCTEXT("PresetSelected", "Selected {0}."), Choice->GetDisplayText());
UE_LOG(LogQwenTTSStreaming, Log, TEXT("Selected Qwen TTS preset. Label=%s Id=%s"), *Choice->Label, *Choice->Id);
}
}
TSharedRef<SWidget> FQwenTTSPresetDetailsCustomization::MakePresetComboWidget(TSharedPtr<FQwenTTSPresetChoice> Choice) const
{
return SNew(STextBlock)
.Text(Choice.IsValid() ? Choice->GetDisplayText() : LOCTEXT("InvalidPresetChoice", "Invalid preset"))
.ToolTipText(Choice.IsValid() ? FText::FromString(Choice->RefText) : FText::GetEmpty());
}
#undef LOCTEXT_NAMESPACE